diff options
494 files changed, 9557 insertions, 6490 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 1b9cf2648a3f..7393bcde13b6 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -27,7 +27,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; +import android.compat.annotation.Disabled; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -282,15 +282,14 @@ public class AlarmManager { public static final long ENABLE_USE_EXACT_ALARM = 218533173L; /** - * For apps targeting {@link Build.VERSION_CODES#TIRAMISU} or above, the permission - * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the user explicitly - * allows it from Settings. + * The permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the + * user explicitly allows it from Settings. * - * TODO (b/226439802): change to EnabledSince(T) after SDK finalization. + * TODO (b/226439802): Either enable it in the next SDK or replace it with a better alternative. * @hide */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) + @Disabled public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L; @UnsupportedAppUsage diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 2ea8592e883e..7b77e86f4d6a 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -565,9 +565,6 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = "kill_on_schedule_exact_alarm_revoked"; - @VisibleForTesting - static final String KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = - "schedule_exact_alarm_denied_by_default"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -612,8 +609,6 @@ public class AlarmManagerService extends SystemService { private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true; - private static final boolean DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true; - // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -701,14 +696,6 @@ public class AlarmManagerService extends SystemService { public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED; - /** - * When this is {@code true}, apps with the change - * {@link AlarmManager#SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT} enabled will not get - * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} unless the user grants it to them. - */ - public volatile boolean SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = - DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; - public boolean USE_TARE_POLICY = Settings.Global.DEFAULT_ENABLE_TARE == 1; private long mLastAllowWhileIdleWhitelistDuration = -1; @@ -892,15 +879,6 @@ public class AlarmManagerService extends SystemService { KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED, DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); break; - case KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT: - final boolean oldValue = SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; - - SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = properties.getBoolean( - KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, - DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT); - handleScheduleExactAlarmDeniedByDefaultChange(oldValue, - SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT); - break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -971,15 +949,6 @@ public class AlarmManagerService extends SystemService { } } - private void handleScheduleExactAlarmDeniedByDefaultChange(boolean oldValue, - boolean newValue) { - if (oldValue == newValue) { - return; - } - mHandler.obtainMessage(AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE, - newValue).sendToTarget(); - } - private void migrateAlarmsToNewStoreLocked() { final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore() : new BatchingAlarmStore(); @@ -1156,9 +1125,6 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED, KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED); pw.println(); - pw.print(KEY_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, - SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT); - pw.println(); pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY); pw.println(); @@ -2928,10 +2894,8 @@ public class AlarmManagerService extends SystemService { } private boolean isScheduleExactAlarmDeniedByDefault(String packageName, int userId) { - return mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT - && CompatChanges.isChangeEnabled( - AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, packageName, - UserHandle.of(userId)); + return CompatChanges.isChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, + packageName, UserHandle.of(userId)); } @NeverCompile // Avoid size overhead of debugging code. @@ -4707,7 +4671,6 @@ public class AlarmManagerService extends SystemService { public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; public static final int TARE_AFFORDABILITY_CHANGED = 12; public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; - public static final int CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE = 14; AlarmHandler() { super(Looper.myLooper()); @@ -4827,32 +4790,6 @@ public class AlarmManagerService extends SystemService { removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */false); } break; - case CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE: - final boolean defaultDenied = (Boolean) msg.obj; - - final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds(); - for (int appId : mExactAlarmCandidates) { - for (int userId : startedUserIds) { - uid = UserHandle.getUid(userId, appId); - - final AndroidPackage packageForUid = - mPackageManagerInternal.getPackage(uid); - if (packageForUid == null) { - continue; - } - final String pkg = packageForUid.getPackageName(); - if (defaultDenied) { - if (!hasScheduleExactAlarmInternal(pkg, uid) - && !hasUseExactAlarmInternal(pkg, uid)) { - removeExactAlarmsOnPermissionRevoked(uid, pkg, true); - } - } else if (hasScheduleExactAlarmInternal(pkg, uid)) { - sendScheduleExactAlarmPermissionStateChangedBroadcast(pkg, - UserHandle.getUserId(uid)); - } - } - } - break; default: // nope, just ignore it break; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2c0b6e9ee89d..fe99c71d9a9a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -314,14 +314,12 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { method public int getOriginalImportance(); method public boolean isImportanceLockedByCriticalDeviceFunction(); - method public boolean isImportanceLockedByOEM(); method public void lockFields(int); 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 setImportanceLockedByOEM(boolean); method public void setImportantConversation(boolean); method public void setOriginalImportance(int); } diff --git a/core/java/Android.bp b/core/java/Android.bp index f081a439c49c..7f9485b6ea45 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -15,6 +15,14 @@ filegroup { "**/*.java", "**/*.aidl", ], + exclude_srcs: [ + // Remove election toolbar code from build time + "android/service/selectiontoolbar/*.aidl", + "android/service/selectiontoolbar/*.java", + "android/view/selectiontoolbar/*.aidl", + "android/view/selectiontoolbar/*.java", + "com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java", + ], visibility: ["//frameworks/base"], } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index abd60177f884..5d1d225f4d2d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4617,8 +4617,8 @@ public class ActivityManager { try { getService().broadcastIntentWithFeature( null, null, intent, null, null, Activity.RESULT_OK, null, null, - null /*requiredPermissions*/, null /*excludedPermissions*/, appOp, null, false, - true, userId); + null /*requiredPermissions*/, null /*excludedPermissions*/, + null /*excludedPackages*/, appOp, null, false, true, userId); } catch (RemoteException ex) { } } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 49a5c9f78230..f2ea060b1694 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -568,14 +568,15 @@ public abstract class ActivityManagerInternal { public abstract void unregisterProcessObserver(IProcessObserver processObserver); /** - * Checks if there is an unfinished instrumentation that targets the given uid. + * Gets the uid of the instrumentation source if there is an unfinished instrumentation that + * targets the given uid. * * @param uid The uid to be checked for * - * @return True, if there is an instrumentation whose target application uid matches the given - * uid, false otherwise + * @return the uid of the instrumentation source, if there is an instrumentation whose target + * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise. */ - public abstract boolean isUidCurrentlyInstrumented(int uid); + public abstract int getInstrumentationSourceUid(int uid); /** Is this a device owner app? */ public abstract boolean isDeviceOwner(int uid); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 3b1943bf86f6..a216021fc66b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1335,9 +1335,17 @@ public class AppOpsManager { public static final int OP_ACCESS_RESTRICTED_SETTINGS = AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS; + /** + * Receive microphone audio from an ambient sound detection event + * + * @hide + */ + public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO = + AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 120; + public static final int _NUM_OP = 121; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1800,6 +1808,14 @@ public class AppOpsManager { public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings"; + /** + * Receive microphone audio from an ambient sound detection event + * + * @hide + */ + public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = + "android:receive_ambient_trigger_audio"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2021,6 +2037,7 @@ public class AppOpsManager { OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER OP_ACCESS_RESTRICTED_SETTINGS, // OP_ACCESS_RESTRICTED_SETTINGS + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, // RECEIVE_SOUNDTRIGGER_AUDIO }; /** @@ -2147,6 +2164,7 @@ public class AppOpsManager { OPSTR_ESTABLISH_VPN_SERVICE, OPSTR_ESTABLISH_VPN_MANAGER, OPSTR_ACCESS_RESTRICTED_SETTINGS, + OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, }; /** @@ -2274,6 +2292,7 @@ public class AppOpsManager { "ESTABLISH_VPN_SERVICE", "ESTABLISH_VPN_MANAGER", "ACCESS_RESTRICTED_SETTINGS", + "RECEIVE_SOUNDTRIGGER_AUDIO", }; /** @@ -2402,6 +2421,7 @@ public class AppOpsManager { null, // no permission for OP_ESTABLISH_VPN_SERVICE null, // no permission for OP_ESTABLISH_VPN_MANAGER null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS, + null, // no permission for OP_RECEIVE_SOUNDTRIGGER_AUDIO }; /** @@ -2529,7 +2549,8 @@ public class AppOpsManager { null, // NEARBY_WIFI_DEVICES null, // ESTABLISH_VPN_SERVICE null, // ESTABLISH_VPN_MANAGER - null, // ACCESS_RESTRICTED_SETTINGS, + null, // ACCESS_RESTRICTED_SETTINGS + null, // RECEIVE_SOUNDTRIGGER_AUDIO }; /** @@ -2656,7 +2677,8 @@ public class AppOpsManager { null, // NEARBY_WIFI_DEVICES null, // ESTABLISH_VPN_SERVICE null, // ESTABLISH_VPN_MANAGER - null, // ACCESS_RESTRICTED_SETTINGS, + null, // ACCESS_RESTRICTED_SETTINGS + null, // RECEIVE_SOUNDTRIGGER_AUDIO }; /** @@ -2765,7 +2787,7 @@ public class AppOpsManager { AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA - AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD + AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_HOTWORD AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER @@ -2783,6 +2805,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS, + AppOpsManager.MODE_ALLOWED, // RECEIVE_SOUNDTRIGGER_AUDIO }; /** @@ -2913,6 +2936,7 @@ public class AppOpsManager { false, // OP_ESTABLISH_VPN_SERVICE false, // OP_ESTABLISH_VPN_MANAGER true, // ACCESS_RESTRICTED_SETTINGS + false, // RECEIVE_SOUNDTRIGGER_AUDIO }; /** @@ -3040,6 +3064,7 @@ public class AppOpsManager { false, // OP_ESTABLISH_VPN_SERVICE false, // OP_ESTABLISH_VPN_MANAGER true, // ACCESS_RESTRICTED_SETTINGS + false, // RECEIVE_SOUNDTRIGGER_AUDIO }; /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ac46066997ff..6d982ced385c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1193,7 +1193,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/, - AppOpsManager.OP_NONE, null, false, false, getUserId()); + null, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1210,7 +1210,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false, + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1226,7 +1226,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false, + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1243,8 +1243,8 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false, - getUserId()); + null /*excludedPermissions=*/, null /*excludedPackages*/, + AppOpsManager.OP_NONE, options, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1259,7 +1259,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false, + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, false, false, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1268,7 +1268,7 @@ class ContextImpl extends Context { @Override public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions, - String[] excludedPermissions) { + String[] excludedPermissions, String[] excludedPackages) { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { @@ -1276,7 +1276,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, excludedPermissions, - AppOpsManager.OP_NONE, null, false, false, getUserId()); + excludedPackages, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1303,7 +1303,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - excludedPermissions, AppOpsManager.OP_NONE, options, false, false, + excludedPermissions, null, AppOpsManager.OP_NONE, options, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1321,7 +1321,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, appOp, null, false, false, getUserId()); + null /*excludedPermissions=*/, null, appOp, null, false, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1338,7 +1338,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, false, + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1402,7 +1402,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermissions, - null /*excludedPermissions=*/, appOp, options, true, false, getUserId()); + null /*excludedPermissions=*/, null, appOp, options, true, false, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1416,7 +1416,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/, - AppOpsManager.OP_NONE, null, false, false, user.getIdentifier()); + null, AppOpsManager.OP_NONE, null, false, false, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1439,8 +1439,8 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false, - user.getIdentifier()); + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, options, false, + false, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1457,7 +1457,8 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermissions, - null /*excludedPermissions=*/, appOp, null, false, false, user.getIdentifier()); + null /*excludedPermissions=*/, null, appOp, null, false, false, + user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1508,7 +1509,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermissions, - null /*excludedPermissions=*/, appOp, options, true, false, + null /*excludedPermissions=*/, null, appOp, options, true, false, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1550,7 +1551,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/, - AppOpsManager.OP_NONE, null, false, true, getUserId()); + null, AppOpsManager.OP_NONE, null, false, true, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1589,7 +1590,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/, - AppOpsManager.OP_NONE, options, false, true, getUserId()); + null, AppOpsManager.OP_NONE, options, false, true, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1625,7 +1626,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true, + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, true, getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1658,7 +1659,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/, - AppOpsManager.OP_NONE, null, false, true, user.getIdentifier()); + null, AppOpsManager.OP_NONE, null, false, true, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1673,7 +1674,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/, - AppOpsManager.OP_NONE, options, false, true, user.getIdentifier()); + null, AppOpsManager.OP_NONE, options, false, true, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1708,7 +1709,7 @@ class ContextImpl extends Context { ActivityManager.getService().broadcastIntentWithFeature( mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, - null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true, + null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, true, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 4efe9dfe7185..8367441b1b95 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -141,7 +141,7 @@ interface IActivityManager { int broadcastIntentWithFeature(in IApplicationThread caller, in String callingFeatureId, in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, in String resultData, in Bundle map, in String[] requiredPermissions, in String[] excludePermissions, - int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); + in String[] excludePackages, int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId); @UnsupportedAppUsage oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map, diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index df9f2a3cbb25..da6a551175e3 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -77,6 +77,7 @@ interface INotificationManager boolean areNotificationsEnabledForPackage(String pkg, int uid); boolean areNotificationsEnabled(String pkg); int getPackageImportance(String pkg); + boolean isImportanceLocked(String pkg, int uid); List<String> getAllowedAssistantAdjustments(String pkg); void allowAssistantAdjustment(String adjustmentType); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 99d7c63cde42..8984c4292023 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1058,10 +1058,11 @@ public class Instrumentation { } /** - * Sends the key events corresponding to the text to the app being - * instrumented. - * - * @param text The text to be sent. + * Sends the key events that result in the given text being typed into the currently focused + * window, and waits for it to be processed. + * + * @param text The text to be sent. + * @see #sendKeySync(KeyEvent) */ public void sendStringSync(String text) { if (text == null) { @@ -1084,11 +1085,12 @@ public class Instrumentation { } /** - * Send a key event to the currently focused window/view and wait for it to - * be processed. Finished at some point after the recipient has returned - * from its event processing, though it may <em>not</em> have completely - * finished reacting from the event -- for example, if it needs to update - * its display as a result, it may still be in the process of doing that. + * Sends a key event to the currently focused window, and waits for it to be processed. + * <p> + * This method blocks until the recipient has finished handling the event. Note that the + * recipient may <em>not</em> have completely finished reacting from the event when this method + * returns. For example, it may still be in the process of updating its display or UI contents + * upon reacting to the injected event. * * @param event The event to send to the current focus. */ @@ -1116,34 +1118,42 @@ public class Instrumentation { } /** - * Sends an up and down key event sync to the currently focused window. + * Sends up and down key events with the given key code to the currently focused window, and + * waits for it to be processed. * - * @param key The integer keycode for the event. + * @param keyCode The key code for the events to send. + * @see #sendKeySync(KeyEvent) */ - public void sendKeyDownUpSync(int key) { - sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key)); - sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key)); + public void sendKeyDownUpSync(int keyCode) { + sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); + sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); } /** - * Higher-level method for sending both the down and up key events for a - * particular character key code. Equivalent to creating both KeyEvent - * objects by hand and calling {@link #sendKeySync}. The event appears - * as if it came from keyboard 0, the built in one. - * + * Sends up and down key events with the given key code to the currently focused window, and + * waits for it to be processed. + * <p> + * Equivalent to {@link #sendKeyDownUpSync(int)}. + * * @param keyCode The key code of the character to send. + * @see #sendKeySync(KeyEvent) */ public void sendCharacterSync(int keyCode) { - sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); - sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + sendKeyDownUpSync(keyCode); } - + /** - * Dispatch a pointer event. Finished at some point after the recipient has - * returned from its event processing, though it may <em>not</em> have - * completely finished reacting from the event -- for example, if it needs - * to update its display as a result, it may still be in the process of - * doing that. + * Dispatches a pointer event into a window owned by the instrumented application, and waits for + * it to be processed. + * <p> + * If the motion event being injected is targeted at a window that is not owned by the + * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for + * injecting events into all windows. + * <p> + * This method blocks until the recipient has finished handling the event. Note that the + * recipient may <em>not</em> have completely finished reacting from the event when this method + * returns. For example, it may still be in the process of updating its display or UI contents + * upon reacting to the injected event. * * @param event A motion event describing the pointer action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -1155,10 +1165,10 @@ public class Instrumentation { event.setSource(InputDevice.SOURCE_TOUCHSCREEN); } - syncInputTransactionsAndInjectEvent(event); + syncInputTransactionsAndInjectEventIntoSelf(event); } - private void syncInputTransactionsAndInjectEvent(MotionEvent event) { + private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) { final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN || event.isFromSource(InputDevice.SOURCE_MOUSE); final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP; @@ -1169,8 +1179,9 @@ public class Instrumentation { .syncInputTransactions(true /*waitForAnimations*/); } + // Direct the injected event into windows owned by the instrumentation target. InputManager.getInstance().injectInputEvent( - event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); + event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH, Process.myUid()); if (syncAfter) { WindowManagerGlobal.getWindowManagerService() @@ -1182,19 +1193,21 @@ public class Instrumentation { } /** - * Dispatch a trackball event. Finished at some point after the recipient has - * returned from its event processing, though it may <em>not</em> have - * completely finished reacting from the event -- for example, if it needs - * to update its display as a result, it may still be in the process of - * doing that. - * + * Dispatches a trackball event into the currently focused window, and waits for it to be + * processed. + * <p> + * This method blocks until the recipient has finished handling the event. Note that the + * recipient may <em>not</em> have completely finished reacting from the event when this method + * returns. For example, it may still be in the process of updating its display or UI contents + * upon reacting to the injected event. + * * @param event A motion event describing the trackball action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use * {@link SystemClock#uptimeMillis()} as the timebase. */ public void sendTrackballEventSync(MotionEvent event) { validateNotAppThread(); - if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) { + if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) { event.setSource(InputDevice.SOURCE_TRACKBALL); } InputManager.getInstance().injectInputEvent(event, diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 91ab19b9e44a..c9cc1a179102 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -253,7 +253,6 @@ public final class NotificationChannel implements Parcelable { // If this is a blockable system notification channel. private boolean mBlockableSystem = false; private int mAllowBubbles = DEFAULT_ALLOW_BUBBLE; - private boolean mImportanceLockedByOEM; private boolean mImportanceLockedDefaultApp; private String mParentId = null; private String mConversationId = null; @@ -322,13 +321,13 @@ public final class NotificationChannel implements Parcelable { mLightColor = in.readInt(); mBlockableSystem = in.readBoolean(); mAllowBubbles = in.readInt(); - mImportanceLockedByOEM = in.readBoolean(); mOriginalImportance = in.readInt(); mParentId = in.readString(); mConversationId = in.readString(); mDemoted = in.readBoolean(); mImportantConvo = in.readBoolean(); mDeletedTime = in.readLong(); + mImportanceLockedDefaultApp = in.readBoolean(); } @Override @@ -382,13 +381,13 @@ public final class NotificationChannel implements Parcelable { dest.writeInt(mLightColor); dest.writeBoolean(mBlockableSystem); dest.writeInt(mAllowBubbles); - dest.writeBoolean(mImportanceLockedByOEM); dest.writeInt(mOriginalImportance); dest.writeString(mParentId); dest.writeString(mConversationId); dest.writeBoolean(mDemoted); dest.writeBoolean(mImportantConvo); dest.writeLong(mDeletedTime); + dest.writeBoolean(mImportanceLockedDefaultApp); } /** @@ -851,14 +850,6 @@ public final class NotificationChannel implements Parcelable { * @hide */ @TestApi - public void setImportanceLockedByOEM(boolean locked) { - mImportanceLockedByOEM = locked; - } - - /** - * @hide - */ - @TestApi public void setImportanceLockedByCriticalDeviceFunction(boolean locked) { mImportanceLockedDefaultApp = locked; } @@ -867,14 +858,6 @@ public final class NotificationChannel implements Parcelable { * @hide */ @TestApi - public boolean isImportanceLockedByOEM() { - return mImportanceLockedByOEM; - } - - /** - * @hide - */ - @TestApi public boolean isImportanceLockedByCriticalDeviceFunction() { return mImportanceLockedDefaultApp; } @@ -1115,8 +1098,8 @@ public final class NotificationChannel implements Parcelable { out.attributeBoolean(null, ATT_IMP_CONVERSATION, isImportantConversation()); } - // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of - // truth and so aren't written to this xml file + // mImportanceLockedDefaultApp has a different source of truth and so isn't written to + // this xml file out.endTag(null, TAG_CHANNEL); } @@ -1259,7 +1242,6 @@ public final class NotificationChannel implements Parcelable { && Arrays.equals(mVibration, that.mVibration) && Objects.equals(getGroup(), that.getGroup()) && Objects.equals(getAudioAttributes(), that.getAudioAttributes()) - && mImportanceLockedByOEM == that.mImportanceLockedByOEM && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp && mOriginalImportance == that.mOriginalImportance && Objects.equals(getParentChannelId(), that.getParentChannelId()) @@ -1275,7 +1257,7 @@ public final class NotificationChannel implements Parcelable { getUserLockedFields(), isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(), getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles, - mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance, + mImportanceLockedDefaultApp, mOriginalImportance, mParentId, mConversationId, mDemoted, mImportantConvo); result = 31 * result + Arrays.hashCode(mVibration); return result; @@ -1320,7 +1302,6 @@ public final class NotificationChannel implements Parcelable { + ", mAudioAttributes=" + mAudioAttributes + ", mBlockableSystem=" + mBlockableSystem + ", mAllowBubbles=" + mAllowBubbles - + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + ", mOriginalImp=" + mOriginalImportance + ", mParent=" + mParentId diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 6615374f71ec..40192836e0a7 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -230,8 +230,6 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.IContentCaptureManager; import android.view.displayhash.DisplayHashManager; import android.view.inputmethod.InputMethodManager; -import android.view.selectiontoolbar.ISelectionToolbarManager; -import android.view.selectiontoolbar.SelectionToolbarManager; import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; import android.view.translation.ITranslationManager; @@ -365,15 +363,6 @@ public final class SystemServiceRegistry { return new TextClassificationManager(ctx); }}); - registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class, - new CachedServiceFetcher<SelectionToolbarManager>() { - @Override - public SelectionToolbarManager createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE); - return new SelectionToolbarManager(ctx.getOuterContext(), - ISelectionToolbarManager.Stub.asInterface(b)); - }}); - registerService(Context.FONT_SERVICE, FontManager.class, new CachedServiceFetcher<FontManager>() { @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0a2b42121545..63cdfe60d40b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -14569,12 +14569,13 @@ public class DevicePolicyManager { } /** - * Called by Device owner to disable user control over apps. User will not be able to clear - * app data or force-stop packages. + * Called by a device owner or a profile owner to disable user control over apps. User will not + * be able to clear app data or force-stop packages. When called by a device owner, applies to + * all users on the device. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param packages The package names for the apps. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner or a profile owner. */ public void setUserControlDisabledPackages(@NonNull ComponentName admin, @NonNull List<String> packages) { @@ -14589,12 +14590,14 @@ public class DevicePolicyManager { } /** - * Returns the list of packages over which user control is disabled by the device owner. + * Returns the list of packages over which user control is disabled by a device or profile + * owner. * * @param admin which {@link DeviceAdminReceiver} this request is associated with - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ - public @NonNull List<String> getUserControlDisabledPackages(@NonNull ComponentName admin) { + @NonNull + public List<String> getUserControlDisabledPackages(@NonNull ComponentName admin) { throwIfParentInstance("getUserControlDisabledPackages"); if (mService != null) { try { diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index c8033fafbc4e..11b840fd4ad4 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -184,6 +184,12 @@ public final class DevicePolicyResources { PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK"; /** + * Text shown on the CTA link shown to user to set a separate lock for work apps + */ + public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK_ACTION = + PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK_ACTION"; + + /** * Message shown in screen lock picker for setting up a work profile screen lock */ public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE = @@ -1492,6 +1498,45 @@ public final class DevicePolicyResources { * Content description for the work profile lock screen. */ public static final String WORK_LOCK_ACCESSIBILITY = PREFIX + "WORK_LOCK_ACCESSIBILITY"; + + /** + * Notification text displayed when screenshots are blocked by an IT admin. + */ + public static final String SCREENSHOT_BLOCKED_BY_ADMIN = + PREFIX + "SCREENSHOT_BLOCKED_BY_ADMIN"; + + /** + * Message shown when user is almost at the limit of password attempts where the + * profile will be removed. Accepts number of failed attempts and remaining failed + * attempts as params. + */ + public static final String KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE = + PREFIX + "KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE"; + + /** + * Message shown in dialog when user has exceeded the maximum attempts and the profile + * will be removed. Accepts number of failed attempts as a param. + */ + public static final String KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE = + PREFIX + "KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE"; + + /** + * Monitoring dialog subtitle for the section describing VPN. + */ + public static final String QS_DIALOG_MONITORING_VPN_SUBTITLE = + PREFIX + "QS_DIALOG_MONITORING_VPN_SUBTITLE"; + + /** + * Monitoring dialog subtitle for the section describing network logging. + */ + public static final String QS_DIALOG_MONITORING_NETWORK_SUBTITLE = + PREFIX + "QS_DIALOG_MONITORING_NETWORK_SUBTITLE"; + + /** + * Monitoring dialog subtitle for the section describing certificate authorities. + */ + public static final String QS_DIALOG_MONITORING_CA_CERT_SUBTITLE = + PREFIX + "QS_DIALOG_MONITORING_CA_CERT_SUBTITLE"; } /** diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 4c1a36340d5d..ab48791d43ef 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -41,6 +41,7 @@ import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYM import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; import static com.android.internal.widget.PasswordValidationError.TOO_LONG; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; +import static com.android.internal.widget.PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC; import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE; import android.annotation.IntDef; @@ -569,21 +570,15 @@ public final class PasswordMetrics implements Parcelable { result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH)); } - // A flag indicating whether the provided password already has non-numeric characters in - // it or if the admin imposes the requirement of any non-numeric characters. - final boolean hasOrWouldNeedNonNumeric = - actualMetrics.nonNumeric > 0 || adminMetrics.nonNumeric > 0 - || adminMetrics.letters > 0 || adminMetrics.lowerCase > 0 - || adminMetrics.upperCase > 0 || adminMetrics.symbols > 0; - final PasswordMetrics minMetrics = - applyComplexity(adminMetrics, hasOrWouldNeedNonNumeric, bucket); + final PasswordMetrics minMetrics = applyComplexity(adminMetrics, + actualMetrics.credType == CREDENTIAL_TYPE_PIN, bucket); // Clamp required length between maximum and minimum valid values. minMetrics.length = Math.min(MAX_PASSWORD_LENGTH, Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE)); minMetrics.removeOverlapping(); - comparePasswordMetrics(minMetrics, actualMetrics, result); + comparePasswordMetrics(minMetrics, bucket, actualMetrics, result); return result; } @@ -591,11 +586,23 @@ public final class PasswordMetrics implements Parcelable { /** * TODO: move to PasswordPolicy */ - private static void comparePasswordMetrics(PasswordMetrics minMetrics, + private static void comparePasswordMetrics(PasswordMetrics minMetrics, ComplexityBucket bucket, PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) { if (actualMetrics.length < minMetrics.length) { result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length)); } + if (actualMetrics.nonNumeric == 0 && minMetrics.nonNumeric == 0 && minMetrics.letters == 0 + && minMetrics.lowerCase == 0 && minMetrics.upperCase == 0 + && minMetrics.symbols == 0) { + // When provided password is all numeric and all numeric password is allowed. + int allNumericMinimumLength = bucket.getMinimumLength(false); + if (allNumericMinimumLength > minMetrics.length + && allNumericMinimumLength > minMetrics.numeric + && actualMetrics.length < allNumericMinimumLength) { + result.add(new PasswordValidationError( + TOO_SHORT_WHEN_ALL_NUMERIC, allNumericMinimumLength)); + } + } if (actualMetrics.letters < minMetrics.letters) { result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters)); } @@ -668,15 +675,12 @@ public final class PasswordMetrics implements Parcelable { * * TODO: move to PasswordPolicy */ - public static PasswordMetrics applyComplexity( - PasswordMetrics adminMetrics, boolean withNonNumericCharacters, + public static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin, int complexity) { - return applyComplexity(adminMetrics, withNonNumericCharacters, - ComplexityBucket.forComplexity(complexity)); + return applyComplexity(adminMetrics, isPin, ComplexityBucket.forComplexity(complexity)); } - private static PasswordMetrics applyComplexity( - PasswordMetrics adminMetrics, boolean withNonNumericCharacters, + private static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin, ComplexityBucket bucket) { final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics); @@ -684,8 +688,7 @@ public final class PasswordMetrics implements Parcelable { minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE); } - minMetrics.length = Math.max(minMetrics.length, - bucket.getMinimumLength(withNonNumericCharacters)); + minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin)); return minMetrics; } diff --git a/core/java/android/app/admin/ProvisioningIntentHelper.java b/core/java/android/app/admin/ProvisioningIntentHelper.java index fbad90c30d7e..1c38559a4083 100644 --- a/core/java/android/app/admin/ProvisioningIntentHelper.java +++ b/core/java/android/app/admin/ProvisioningIntentHelper.java @@ -17,8 +17,10 @@ package android.app.admin; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE; +import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME; +import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER; import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC; import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_NFC; @@ -36,12 +38,14 @@ import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Parcelable; +import android.os.PersistableBundle; import android.util.Log; import java.io.IOException; import java.io.StringReader; import java.util.Enumeration; import java.util.Properties; +import java.util.Set; /** * Utility class that provides functionality to create provisioning intents from nfc intents. @@ -124,12 +128,46 @@ final class ProvisioningIntentHelper { ComponentName componentName = ComponentName.unflattenFromString( properties.getProperty(propertyName)); bundle.putParcelable(propertyName, componentName); + } else if (EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE.equals(propertyName) + || EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE.equals(propertyName)) { + try { + bundle.putParcelable(propertyName, + deserializeExtrasBundle(properties, propertyName)); + } catch (IOException e) { + Log.e(TAG, + "Failed to parse " + propertyName + ".", e); + } } else { bundle.putString(propertyName, properties.getProperty(propertyName)); } } + /** + * Get a {@link PersistableBundle} from a {@code String} property in a {@link Properties} + * object. + * @param properties the source of the extra + * @param extraName key into the {@link Properties} object + * @return the {@link PersistableBundle} or {@code null} if there was no property with the + * given name + * @throws IOException if there was an error parsing the property + */ + private static PersistableBundle deserializeExtrasBundle( + Properties properties, String extraName) throws IOException { + String serializedExtras = properties.getProperty(extraName); + if (serializedExtras == null) { + return null; + } + Properties bundleProperties = new Properties(); + bundleProperties.load(new StringReader(serializedExtras)); + PersistableBundle extrasBundle = new PersistableBundle(bundleProperties.size()); + Set<String> propertyNames = bundleProperties.stringPropertyNames(); + for (String propertyName : propertyNames) { + extrasBundle.putString(propertyName, bundleProperties.getProperty(propertyName)); + } + return extrasBundle; + } + private static Intent createProvisioningIntentFromBundle(Bundle bundle) { requireNonNull(bundle); diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index 9bb048d7a75f..f6de72b43de6 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -665,184 +665,292 @@ public class BackupTransport { @Override public void name(AndroidFuture<String> resultFuture) throws RemoteException { - String result = BackupTransport.this.name(); - resultFuture.complete(result); + try { + String result = BackupTransport.this.name(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void configurationIntent(AndroidFuture<Intent> resultFuture) throws RemoteException { - Intent result = BackupTransport.this.configurationIntent(); - resultFuture.complete(result); + try { + Intent result = BackupTransport.this.configurationIntent(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void currentDestinationString(AndroidFuture<String> resultFuture) throws RemoteException { - String result = BackupTransport.this.currentDestinationString(); - resultFuture.complete(result); + try { + String result = BackupTransport.this.currentDestinationString(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void dataManagementIntent(AndroidFuture<Intent> resultFuture) throws RemoteException { - Intent result = BackupTransport.this.dataManagementIntent(); - resultFuture.complete(result); + try { + Intent result = BackupTransport.this.dataManagementIntent(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void dataManagementIntentLabel(AndroidFuture<CharSequence> resultFuture) throws RemoteException { - CharSequence result = BackupTransport.this.dataManagementIntentLabel(); - resultFuture.complete(result); + try { + CharSequence result = BackupTransport.this.dataManagementIntentLabel(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void transportDirName(AndroidFuture<String> resultFuture) throws RemoteException { - String result = BackupTransport.this.transportDirName(); - resultFuture.complete(result); + try { + String result = BackupTransport.this.transportDirName(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void requestBackupTime(AndroidFuture<Long> resultFuture) throws RemoteException { - long result = BackupTransport.this.requestBackupTime(); - resultFuture.complete(result); + try { + long result = BackupTransport.this.requestBackupTime(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void initializeDevice(ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.initializeDevice(); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.initializeDevice(); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.performBackup(packageInfo, inFd, flags); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.performBackup(packageInfo, inFd, flags); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void clearBackupData(PackageInfo packageInfo, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.clearBackupData(packageInfo); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.clearBackupData(packageInfo); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void finishBackup(ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.finishBackup(); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.finishBackup(); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void getAvailableRestoreSets(AndroidFuture<List<RestoreSet>> resultFuture) throws RemoteException { - RestoreSet[] result = BackupTransport.this.getAvailableRestoreSets(); - resultFuture.complete(Arrays.asList(result)); + try { + RestoreSet[] result = BackupTransport.this.getAvailableRestoreSets(); + resultFuture.complete(Arrays.asList(result)); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void getCurrentRestoreSet(AndroidFuture<Long> resultFuture) throws RemoteException { - long result = BackupTransport.this.getCurrentRestoreSet(); - resultFuture.complete(result); + try { + long result = BackupTransport.this.getCurrentRestoreSet(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void startRestore(long token, PackageInfo[] packages, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.startRestore(token, packages); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.startRestore(token, packages); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void nextRestorePackage(AndroidFuture<RestoreDescription> resultFuture) throws RemoteException { - RestoreDescription result = BackupTransport.this.nextRestorePackage(); - resultFuture.complete(result); + try { + RestoreDescription result = BackupTransport.this.nextRestorePackage(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void getRestoreData(ParcelFileDescriptor outFd, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.getRestoreData(outFd); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.getRestoreData(outFd); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void finishRestore(ITransportStatusCallback callback) throws RemoteException { - BackupTransport.this.finishRestore(); - callback.onOperationComplete(); + try { + BackupTransport.this.finishRestore(); + callback.onOperationComplete(); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void requestFullBackupTime(AndroidFuture<Long> resultFuture) throws RemoteException { - long result = BackupTransport.this.requestFullBackupTime(); - resultFuture.complete(result); + try { + long result = BackupTransport.this.requestFullBackupTime(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, int flags, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.performFullBackup(targetPackage, socket, flags); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.performFullBackup(targetPackage, socket, flags); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void checkFullBackupSize(long size, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.checkFullBackupSize(size); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.checkFullBackupSize(size); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void sendBackupData(int numBytes, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.sendBackupData(numBytes); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.sendBackupData(numBytes); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void cancelFullBackup(ITransportStatusCallback callback) throws RemoteException { - BackupTransport.this.cancelFullBackup(); - callback.onOperationComplete(); + try { + BackupTransport.this.cancelFullBackup(); + callback.onOperationComplete(); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup, AndroidFuture<Boolean> resultFuture) throws RemoteException { - boolean result = BackupTransport.this.isAppEligibleForBackup(targetPackage, - isFullBackup); - resultFuture.complete(result); + try { + boolean result = BackupTransport.this.isAppEligibleForBackup(targetPackage, + isFullBackup); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void getBackupQuota(String packageName, boolean isFullBackup, AndroidFuture<Long> resultFuture) throws RemoteException { - long result = BackupTransport.this.getBackupQuota(packageName, isFullBackup); - resultFuture.complete(result); + try { + long result = BackupTransport.this.getBackupQuota(packageName, isFullBackup); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void getTransportFlags(AndroidFuture<Integer> resultFuture) throws RemoteException { - int result = BackupTransport.this.getTransportFlags(); - resultFuture.complete(result); + try { + int result = BackupTransport.this.getTransportFlags(); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } } @Override public void getNextFullRestoreDataChunk(ParcelFileDescriptor socket, ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.getNextFullRestoreDataChunk(socket); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.getNextFullRestoreDataChunk(socket); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } @Override public void abortFullRestore(ITransportStatusCallback callback) throws RemoteException { - int result = BackupTransport.this.abortFullRestore(); - callback.onOperationCompleteWithStatus(result); + try { + int result = BackupTransport.this.abortFullRestore(); + callback.onOperationCompleteWithStatus(result); + } catch (RuntimeException e) { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + } } } } diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java index be077435b080..79d7b216628f 100644 --- a/core/java/android/app/smartspace/SmartspaceTarget.java +++ b/core/java/android/app/smartspace/SmartspaceTarget.java @@ -174,7 +174,7 @@ public final class SmartspaceTarget implements Parcelable { public static final int FEATURE_MEDIA_HEADS_UP = 36; public static final int FEATURE_STEP_COUNTING = 37; public static final int FEATURE_EARTHQUAKE_ALERT = 38; - public static final int FEATURE_STEP_DATE = 39; + public static final int FEATURE_STEP_DATE = 39; // This represents a DATE. "STEP" is a typo. public static final int FEATURE_BLAZE_BUILD_PROGRESS = 40; public static final int FEATURE_EARTHQUAKE_OCCURRED = 41; @@ -283,7 +283,7 @@ public final class SmartspaceTarget implements Parcelable { this.mAssociatedSmartspaceTargetId = in.readString(); this.mSliceUri = in.readTypedObject(Uri.CREATOR); this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR); - this.mTemplateData = in.readTypedObject(BaseTemplateData.CREATOR); + this.mTemplateData = in.readParcelable(/* loader= */null, BaseTemplateData.class); } private SmartspaceTarget(String smartspaceTargetId, @@ -491,7 +491,7 @@ public final class SmartspaceTarget implements Parcelable { dest.writeString(this.mAssociatedSmartspaceTargetId); dest.writeTypedObject(this.mSliceUri, flags); dest.writeTypedObject(this.mWidget, flags); - dest.writeTypedObject(this.mTemplateData, flags); + dest.writeParcelable(this.mTemplateData, flags); } @Override diff --git a/core/java/android/companion/virtual/OWNERS b/core/java/android/companion/virtual/OWNERS new file mode 100644 index 000000000000..29681045ac4a --- /dev/null +++ b/core/java/android/companion/virtual/OWNERS @@ -0,0 +1 @@ +include /services/companion/java/com/android/server/companion/virtual/OWNERS diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 907db7df68d5..836bff598ede 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2269,6 +2269,19 @@ public abstract class Context { */ public void sendBroadcastMultiplePermissions(@NonNull Intent intent, @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) { + sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions, null); + } + + + /** + * Like {@link #sendBroadcastMultiplePermissions(Intent, String[], String[])}, but also allows + * specification of a list of excluded packages. + * + * @hide + */ + public void sendBroadcastMultiplePermissions(@NonNull Intent intent, + @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions, + @Nullable String[] excludedPackages) { throw new RuntimeException("Not implemented. Must override in a subclass."); } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 4ecd7761ac4f..e6549187e5c5 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -515,8 +515,10 @@ public class ContextWrapper extends Context { /** @hide */ @Override public void sendBroadcastMultiplePermissions(@NonNull Intent intent, - @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) { - mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions); + @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions, + @Nullable String[] excludedPackages) { + mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions, + excludedPackages); } /** @hide */ diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 1b84686bbfcf..fb41b890ce9c 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -413,7 +413,10 @@ public class AppSearchShortcutInfo extends GenericDocument { final int iconResId = (int) getPropertyLong(KEY_ICON_RES_ID); final String iconResName = getPropertyString(KEY_ICON_RES_NAME); final String iconUri = getPropertyString(KEY_ICON_URI); - final int disabledReason = Integer.parseInt(getPropertyString(KEY_DISABLED_REASON)); + final String disabledReasonString = getPropertyString(KEY_DISABLED_REASON); + final int disabledReason = !TextUtils.isEmpty(disabledReasonString) + ? Integer.parseInt(getPropertyString(KEY_DISABLED_REASON)) + : ShortcutInfo.DISABLED_REASON_NOT_DISABLED; final Map<String, Map<String, List<String>>> capabilityBindings = parseCapabilityBindings(getPropertyStringArray(KEY_CAPABILITY_BINDINGS)); return new ShortcutInfo( diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index cb55e303e778..20a4fdf658c6 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -567,9 +567,14 @@ public class ApkLiteParseUtils { targetCode = minCode; } + boolean allowUnknownCodenames = false; + if ((flags & FrameworkParsingPackageUtils.PARSE_APK_IN_APEX) != 0) { + allowUnknownCodenames = true; + } + ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion( targetVer, targetCode, SDK_CODENAMES, input, - /* allowUnknownCodenames= */ false); + allowUnknownCodenames); if (targetResult.isError()) { return input.error(targetResult); } diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java index bde71bb90bf7..8cc4cdb955ca 100644 --- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -58,6 +58,7 @@ public class FrameworkParsingPackageUtils { private static final int MAX_FILE_NAME_SIZE = 223; public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; + public static final int PARSE_APK_IN_APEX = 1 << 9; /** * Check if the given name is valid. diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index e1ffd4a6761d..2da12e6c5c9d 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -57,11 +57,16 @@ interface IInputManager { // Temporarily changes the pointer speed. void tryPointerSpeed(int speed); - // Injects an input event into the system. To inject into windows owned by other - // applications, the caller must have the INJECT_EVENTS permission. + // Injects an input event into the system. The caller must have the INJECT_EVENTS permssion. + // This method exists only for compatibility purposes and may be removed in a future release. @UnsupportedAppUsage boolean injectInputEvent(in InputEvent ev, int mode); + // Injects an input event into the system. The caller must have the INJECT_EVENTS permission. + // The caller can target windows owned by a certain UID by providing a valid UID, or by + // providing {@link android.os.Process#INVALID_UID} to target all windows. + boolean injectInputEventToTarget(in InputEvent ev, int mode, int targetUid); + VerifiedInputEvent verifyInputEvent(in InputEvent ev); // Calibrate input device position diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index cc5b275bbf5a..d17a9523ab37 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -45,6 +45,7 @@ import android.os.IVibratorStateListener; import android.os.InputEventInjectionSync; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; @@ -1107,14 +1108,18 @@ public final class InputManager { } } - /** - * Injects an input event into the event system on behalf of an application. + * Injects an input event into the event system, targeting windows owned by the provided uid. + * + * If a valid targetUid is provided, the system will only consider injecting the input event + * into windows owned by the provided uid. If the input event is targeted at a window that is + * not owned by the provided uid, input injection will fail and a RemoteException will be + * thrown. + * * The synchronization mode determines whether the method blocks while waiting for * input injection to proceed. * <p> - * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into - * windows that are owned by other applications. + * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission. * </p><p> * Make sure you correctly set the event time and input source of the event * before calling this method. @@ -1125,12 +1130,14 @@ public final class InputManager { * {@link android.os.InputEventInjectionSync.NONE}, * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}. + * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all + * windows. * @return True if input event injection succeeded. * * @hide */ - @UnsupportedAppUsage - public boolean injectInputEvent(InputEvent event, int mode) { + @RequiresPermission(Manifest.permission.INJECT_EVENTS) + public boolean injectInputEvent(InputEvent event, int mode, int targetUid) { if (event == null) { throw new IllegalArgumentException("event must not be null"); } @@ -1141,13 +1148,39 @@ public final class InputManager { } try { - return mIm.injectInputEvent(event, mode); + return mIm.injectInputEventToTarget(event, mode, targetUid); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** + * Injects an input event into the event system on behalf of an application. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. + * <p> + * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission. + * </p><p> + * Make sure you correctly set the event time and input source of the event + * before calling this method. + * </p> + * + * @param event The event to inject. + * @param mode The synchronization mode. One of: + * {@link android.os.InputEventInjectionSync.NONE}, + * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or + * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}. + * @return True if input event injection succeeded. + * + * @hide + */ + @RequiresPermission(Manifest.permission.INJECT_EVENTS) + @UnsupportedAppUsage + public boolean injectInputEvent(InputEvent event, int mode) { + return injectInputEvent(event, mode, Process.INVALID_UID); + } + + /** * Verify the details of an {@link android.view.InputEvent} that came from the system. * If the event did not come from the system, or its details could not be verified, then this * will return {@code null}. Receiving {@code null} does not mean that the event did not diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index ae7d91f92cb7..37eb74a58235 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -187,14 +187,24 @@ public class VpnManager { /** * The network that was underlying the VPN when the event occurred, as a {@link Network}. * - * This extra will be null if there was no underlying network at the time of the event. + * <p>This extra will be null if there was no underlying network at the time of the event, or + * the underlying network has no bearing on the event, as in the case of: + * <ul> + * <li>CATEGORY_EVENT_DEACTIVATED_BY_USER + * <li>CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED + * </ul> */ public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK"; /** * The {@link NetworkCapabilities} of the underlying network when the event occurred. * - * This extra will be null if there was no underlying network at the time of the event. + * <p>This extra will be null if there was no underlying network at the time of the event, or + * the underlying network has no bearing on the event, as in the case of: + * <ul> + * <li>CATEGORY_EVENT_DEACTIVATED_BY_USER + * <li>CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED + * </ul> */ public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES"; @@ -202,7 +212,12 @@ public class VpnManager { /** * The {@link LinkProperties} of the underlying network when the event occurred. * - * This extra will be null if there was no underlying network at the time of the event. + * <p>This extra will be null if there was no underlying network at the time of the event, or + * the underlying network has no bearing on the event, as in the case of: + * <ul> + * <li>CATEGORY_EVENT_DEACTIVATED_BY_USER + * <li>CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED + * </ul> */ public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES"; diff --git a/core/java/android/net/VpnProfileState.java b/core/java/android/net/VpnProfileState.java index c69ea1a8c220..552a2c171f21 100644 --- a/core/java/android/net/VpnProfileState.java +++ b/core/java/android/net/VpnProfileState.java @@ -24,6 +24,8 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.StringJoiner; /** * Describe the state of VPN. @@ -150,4 +152,44 @@ public final class VpnProfileState implements Parcelable { mAlwaysOn = in.readBoolean(); mLockdown = in.readBoolean(); } + + private String convertStateToString(@State int state) { + switch (state) { + case STATE_CONNECTED: + return "CONNECTED"; + case STATE_CONNECTING: + return "CONNECTING"; + case STATE_DISCONNECTED: + return "DISCONNECTED"; + case STATE_FAILED: + return "FAILED"; + default: + return "UNKNOWN"; + } + } + + @Override + public String toString() { + final StringJoiner resultJoiner = new StringJoiner(", ", "{", "}"); + resultJoiner.add("State: " + convertStateToString(getState())); + resultJoiner.add("SessionId: " + getSessionId()); + resultJoiner.add("Always-on: " + isAlwaysOn()); + resultJoiner.add("Lockdown: " + isLockdownEnabled()); + return resultJoiner.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof VpnProfileState)) return false; + final VpnProfileState that = (VpnProfileState) obj; + return (getState() == that.getState() + && Objects.equals(getSessionId(), that.getSessionId()) + && isAlwaysOn() == that.isAlwaysOn() + && isLockdownEnabled() == that.isLockdownEnabled()); + } + + @Override + public int hashCode() { + return Objects.hash(getState(), getSessionId(), isAlwaysOn(), isLockdownEnabled()); + } } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 3c2c7f03761b..b494c7f4026a 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -860,7 +860,7 @@ public final class PermissionControllerManager { Binder.restoreCallingIdentity(token); } } - }); + }, executor); } /** diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 4ed939c48bd7..f5f1c374b636 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -30,6 +30,7 @@ import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION; import static android.app.AppOpsManager.OPSTR_FINE_LOCATION; import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE; +import static android.app.AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; @@ -137,6 +138,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis private static final List<String> MIC_OPS = List.of( OPSTR_PHONE_CALL_MICROPHONE, + OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECORD_AUDIO ); @@ -147,6 +149,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis private static @NonNull String getGroupForOp(String op) { switch (op) { + case OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO: case OPSTR_RECORD_AUDIO: return MICROPHONE; case OPSTR_CAMERA: diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f35a45891545..d2a86eb31870 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10155,15 +10155,6 @@ public final class Settings { public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl"; /** - * Whether the app-level notification setting is represented by a manifest permission. - * - * @hide - */ - @Readable - public static final String NOTIFICATION_PERMISSION_ENABLED = - "notification_permission_enabled"; - - /** * Comma separated list of QS tiles that have been auto-added already. * @hide */ diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 0ec95c687090..425dbb9cb204 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -904,6 +904,7 @@ public abstract class WallpaperService extends Service { // based on its default wallpaper color hints. mShouldDim = dimAmount != 0f || mShouldDimByDefault; updateSurfaceDimming(); + updateSurface(false, false, true); } private void updateSurfaceDimming() { @@ -940,7 +941,6 @@ public abstract class WallpaperService extends Service { } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply(); - updateSurface(false, false, true); } mPreviousWallpaperDimAmount = mWallpaperDimAmount; diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index e5ec260907df..691452f51ee8 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -341,10 +341,12 @@ public class Surface implements Parcelable { */ @UnsupportedAppUsage public void destroy() { - if (mNativeObject != 0) { - nativeDestroy(mNativeObject); + synchronized (mLock) { + if (mNativeObject != 0) { + nativeDestroy(mNativeObject); + } + release(); } - release(); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d04b07c13b41..38ca2481726b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11941,7 +11941,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @NonNull public final List<Rect> getUnrestrictedPreferKeepClearRects() { final ListenerInfo info = mListenerInfo; - if (info != null && info.mKeepClearRects != null) { + if (info != null && info.mUnrestrictedKeepClearRects != null) { return new ArrayList(info.mUnrestrictedKeepClearRects); } @@ -21170,6 +21170,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } AccessibilityNodeIdManager.getInstance().unregisterViewWithId(getAccessibilityViewId()); + + if (mBackgroundRenderNode != null) { + mBackgroundRenderNode.forceEndAnimators(); + } + mRenderNode.forceEndAnimators(); } private void cleanupDraw() { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 5bc340b76f56..00052f6015d5 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3108,10 +3108,14 @@ public interface WindowManager extends ViewManager { /** * The preferred refresh rate for the window. - * + * <p> * This must be one of the supported refresh rates obtained for the display(s) the window * is on. The selected refresh rate will be applied to the display's default mode. - * + * <p> + * This should be used in favor of {@link LayoutParams#preferredDisplayModeId} for + * applications that want to specify the refresh rate, but do not want to specify a + * preference for any other displayMode properties (e.g., resolution). + * <p> * This value is ignored if {@link #preferredDisplayModeId} is set. * * @see Display#getSupportedRefreshRates() diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java index 599e6d24600c..f3a322cac79a 100644 --- a/core/java/com/android/internal/app/AppLocaleStore.java +++ b/core/java/com/android/internal/app/AppLocaleStore.java @@ -20,12 +20,15 @@ import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStat import android.app.LocaleConfig; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; import android.content.pm.PackageManager; import android.os.LocaleList; import android.util.Log; -import java.util.ArrayList; +import java.util.HashSet; import java.util.Locale; +import java.util.stream.Collectors; class AppLocaleStore { private static final String TAG = AppLocaleStore.class.getSimpleName(); @@ -34,7 +37,8 @@ class AppLocaleStore { Context context, String packageName) { LocaleConfig localeConfig = null; AppLocaleResult.LocaleStatus localeStatus = LocaleStatus.UNKNOWN_FAILURE; - ArrayList<Locale> appSupportedLocales = new ArrayList<>(); + HashSet<Locale> appSupportedLocales = new HashSet<>(); + HashSet<Locale> assetLocale = getAssetLocales(context, packageName); try { localeConfig = new LocaleConfig(context.createPackageContext(packageName, 0)); @@ -45,32 +49,43 @@ class AppLocaleStore { if (localeConfig != null) { if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { LocaleList packageLocaleList = localeConfig.getSupportedLocales(); - if (packageLocaleList.size() > 0) { + boolean shouldFilterNotMatchingLocale = !hasInstallerInfo(context, packageName) && + isSystemApp(context, packageName); + + Log.d(TAG, "filterNonMatchingLocale. " + + ", shouldFilterNotMatchingLocale: " + shouldFilterNotMatchingLocale + + ", assetLocale size: " + assetLocale.size() + + ", packageLocaleList size: " + packageLocaleList.size()); + + for (int i = 0; i < packageLocaleList.size(); i++) { + appSupportedLocales.add(packageLocaleList.get(i)); + } + if (shouldFilterNotMatchingLocale) { + appSupportedLocales = filterNotMatchingLocale(appSupportedLocales, assetLocale); + } + + if (appSupportedLocales.size() > 0) { localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG; - for (int i = 0; i < packageLocaleList.size(); i++) { - appSupportedLocales.add(packageLocaleList.get(i)); - } } else { localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE_IN_APP; } } else if (localeConfig.getStatus() == LocaleConfig.STATUS_NOT_SPECIFIED) { - String[] languages = getAssetLocales(context, packageName); - if (languages.length > 0) { + if (assetLocale.size() > 0) { localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; - for (String language : languages) { - appSupportedLocales.add(Locale.forLanguageTag(language)); - } + appSupportedLocales = assetLocale; } else { localeStatus = LocaleStatus.ASSET_LOCALE_IS_EMPTY; } } } - Log.d(TAG, "getAppSupportedLocales(). status: " + localeStatus + Log.d(TAG, "getAppSupportedLocales(). package: " + packageName + + ", status: " + localeStatus + ", appSupportedLocales:" + appSupportedLocales.size()); return new AppLocaleResult(localeStatus, appSupportedLocales); } - private static String[] getAssetLocales(Context context, String packageName) { + private static HashSet<Locale> getAssetLocales(Context context, String packageName) { + HashSet<Locale> result = new HashSet<>(); try { PackageManager packageManager = context.getPackageManager(); String[] locales = packageManager.getResourcesForApplication( @@ -78,16 +93,59 @@ class AppLocaleStore { .applicationInfo).getAssets().getNonSystemLocales(); if (locales == null) { Log.i(TAG, "[" + packageName + "] locales are null."); - return new String[0]; } else if (locales.length <= 0) { Log.i(TAG, "[" + packageName + "] locales length is 0."); - return new String[0]; + } else { + for (String language : locales) { + result.add(Locale.forLanguageTag(language)); + } } - return locales; } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); } - return new String[0]; + return result; + } + + private static HashSet<Locale> filterNotMatchingLocale( + HashSet<Locale> appSupportedLocales, HashSet<Locale> assetLocale) { + return appSupportedLocales.stream() + .filter(locale -> matchLanguageInSet(locale, assetLocale)) + .collect(Collectors.toCollection(HashSet::new)); + } + + private static boolean matchLanguageInSet(Locale locale, HashSet<Locale> localesSet) { + if (localesSet.contains(locale)) { + return true; + } + for (Locale l: localesSet) { + if(LocaleList.matchesLanguageAndScript(l, locale)) { + return true; + } + } + return false; + } + + private static boolean hasInstallerInfo(Context context, String packageName) { + InstallSourceInfo installSourceInfo; + try { + installSourceInfo = context.getPackageManager().getInstallSourceInfo(packageName); + return installSourceInfo != null; + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Installer info not found for: " + packageName); + } + return false; + } + + private static boolean isSystemApp(Context context, String packageName) { + ApplicationInfo applicationInfo; + try { + applicationInfo = context.getPackageManager() + .getApplicationInfoAsUser(packageName, /* flags= */ 0, context.getUserId()); + return applicationInfo.isSystemApp(); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Application info not found for: " + packageName); + } + return false; } static class AppLocaleResult { @@ -100,9 +158,9 @@ class AppLocaleStore { } LocaleStatus mLocaleStatus; - ArrayList<Locale> mAppSupportedLocales; + HashSet<Locale> mAppSupportedLocales; - public AppLocaleResult(LocaleStatus localeStatus, ArrayList<Locale> appSupportedLocales) { + public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) { this.mLocaleStatus = localeStatus; this.mAppSupportedLocales = appSupportedLocales; } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 781b6d5459ca..2ad1b3845bdc 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1861,10 +1861,10 @@ public class ChooserActivity extends ResolverActivity implements try { final Intent intent = getTargetIntent(); String dataString = intent.getDataString(); - if (!TextUtils.isEmpty(dataString)) { - return new IntentFilter(intent.getAction(), dataString); - } if (intent.getType() == null) { + if (!TextUtils.isEmpty(dataString)) { + return new IntentFilter(intent.getAction(), dataString); + } Log.e(TAG, "Failed to get target intent filter: intent data and type are null"); return null; } diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index f6445849d897..8dd827287942 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -144,7 +144,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } } if (ai == null) { - ri = packageManager.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY); + // Because of AIDL bug, resolveActivity can't accept subclasses of Intent. + final Intent rii = (ii.getClass() == Intent.class) ? ii : new Intent(ii); + ri = packageManager.resolveActivity(rii, PackageManager.MATCH_DEFAULT_ONLY); ai = ri != null ? ri.activityInfo : null; } if (ai == null) { diff --git a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java index 4f1f380c3a77..3e1b5f087c10 100644 --- a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java +++ b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java @@ -269,7 +269,7 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment protected CharSequence getItemLabel(DisplayResolveInfo dri) { final PackageManager pm = getContext().getPackageManager(); return getPinLabel(isPinned(dri), - isShortcutTarget() ? "" : dri.getResolveInfo().loadLabel(pm)); + isShortcutTarget() ? mShortcutTitle : dri.getResolveInfo().loadLabel(pm)); } @Nullable diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index a06ba9be4689..965895f08d6e 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -29,13 +29,13 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.MenuItem.OnActionExpandListener; import android.view.View; import android.widget.ListView; import android.widget.SearchView; import com.android.internal.R; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Locale; @@ -64,6 +64,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private int mTopDistance = 0; private String mAppPackageName; private CharSequence mTitle = null; + private OnActionExpandListener mOnActionExpandListener; /** * Other classes can register to be notified when a locale was selected. @@ -80,8 +81,10 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private static LocalePickerWithRegion createCountryPicker(Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, - boolean translatedOnly, String appPackageName) { + boolean translatedOnly, String appPackageName, + OnActionExpandListener onActionExpandListener) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); + localePicker.setOnActionExpandListener(onActionExpandListener); boolean shouldShowTheList = localePicker.setListener(context, listener, parent, translatedOnly, appPackageName); return shouldShowTheList ? localePicker : null; @@ -95,8 +98,10 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O } public static LocalePickerWithRegion createLanguagePicker(Context context, - LocaleSelectedListener listener, boolean translatedOnly, String appPackageName) { + LocaleSelectedListener listener, boolean translatedOnly, String appPackageName, + OnActionExpandListener onActionExpandListener) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); + localePicker.setOnActionExpandListener(onActionExpandListener); localePicker.setListener( context, listener, /* parent */ null, translatedOnly, appPackageName); return localePicker; @@ -198,13 +203,20 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O } private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp( - boolean shouldShowList, ArrayList<Locale> supportedLocales) { + boolean shouldShowList, HashSet<Locale> supportedLocales) { Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); - if (shouldShowList) { - for(LocaleStore.LocaleInfo li: mLocaleList) { + if (!shouldShowList) { + return filteredList; + } + + for(LocaleStore.LocaleInfo li: mLocaleList) { + if (supportedLocales.contains(li.getLocale())) { + filteredList.add(li); + } else { for(Locale l: supportedLocales) { if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) { filteredList.add(li); + break; } } } @@ -310,7 +322,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O } else { LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( getContext(), mListener, locale, mTranslatedOnly /* translate only */, - mAppPackageName); + mAppPackageName, mOnActionExpandListener); if (selector != null) { getFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) @@ -328,8 +340,11 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O inflater.inflate(R.menu.language_selection_list, menu); final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu); - mSearchView = (SearchView) searchMenuItem.getActionView(); + if (!TextUtils.isEmpty(mAppPackageName) && mOnActionExpandListener != null) { + searchMenuItem.setOnActionExpandListener(mOnActionExpandListener); + } + mSearchView = (SearchView) searchMenuItem.getActionView(); mSearchView.setQueryHint(getText(R.string.search_language_hint)); mSearchView.setOnQueryTextListener(this); @@ -363,4 +378,11 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O } return false; } + + /** + * Sets OnActionExpandListener to LocalePickerWithRegion to dectect action of search bar. + */ + public void setOnActionExpandListener(OnActionExpandListener onActionExpandListener) { + mOnActionExpandListener = onActionExpandListener; + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index bd5a73d8194a..7e53a5afc315 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -119,6 +119,11 @@ public class ResolverActivity extends Activity implements @UnsupportedAppUsage public ResolverActivity() { + mIsIntentPicker = getClass().equals(ResolverActivity.class); + } + + protected ResolverActivity(boolean isIntentPicker) { + mIsIntentPicker = isIntentPicker; } private boolean mSafeForwardingMode; @@ -135,6 +140,8 @@ public class ResolverActivity extends Activity implements private String mReferrerPackage; private CharSequence mTitle; private int mDefaultTitleResId; + // Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity. + private final boolean mIsIntentPicker; // Whether or not this activity supports choosing a default handler for the intent. @VisibleForTesting @@ -445,10 +452,6 @@ public class ResolverActivity extends Activity implements + (categories != null ? Arrays.toString(categories.toArray()) : "")); } - private boolean isIntentPicker() { - return getClass().equals(ResolverActivity.class); - } - protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( Intent[] initialIntents, List<ResolveInfo> rList, @@ -637,6 +640,11 @@ public class ResolverActivity extends Activity implements resetButtonBar(); + if (shouldUseMiniResolver()) { + View buttonContainer = findViewById(R.id.button_bar_container); + buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom); + } + // Need extra padding so the list can fully scroll up if (shouldAddFooterView()) { applyFooterView(mSystemWindowInsets.bottom); @@ -649,7 +657,8 @@ public class ResolverActivity extends Activity implements public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); - if (isIntentPicker() && shouldShowTabs() && !useLayoutWithDefault()) { + if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault() + && !shouldUseMiniResolver()) { updateIntentPickerPaddings(); } @@ -1084,7 +1093,7 @@ public class ResolverActivity extends Activity implements if (isAutolaunching()) { return; } - if (isIntentPicker()) { + if (mIsIntentPicker) { ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) .setUseLayoutWithDefault(useLayoutWithDefault()); } @@ -1108,7 +1117,7 @@ public class ResolverActivity extends Activity implements protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) { final ItemClickListener listener = new ItemClickListener(); setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener); - if (shouldShowTabs() && isIntentPicker()) { + if (shouldShowTabs() && mIsIntentPicker) { final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel); if (rdl != null) { rdl.setMaxCollapsedHeight(getResources() @@ -1448,6 +1457,12 @@ public class ResolverActivity extends Activity implements return postRebuildList(rebuildCompleted); } + /** + * Mini resolver is shown when the user is choosing between browser[s] in this profile and a + * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon + * and asks the user if they'd like to open that cross-profile app or use the in-profile + * browser. + */ private void configureMiniResolverContent() { mLayoutId = R.layout.miniresolver; setContentView(mLayoutId); @@ -1456,10 +1471,14 @@ public class ResolverActivity extends Activity implements mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0); boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK; - DisplayResolveInfo otherProfileResolveInfo = - mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList.get(0); + ResolverListAdapter inactiveAdapter = mMultiProfilePagerAdapter.getInactiveListAdapter(); + DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0); + + // Load the icon asynchronously ImageView icon = findViewById(R.id.icon); - // TODO: Set icon drawable to app icon. + ResolverListAdapter.LoadIconTask iconTask = inactiveAdapter.new LoadIconTask( + otherProfileResolveInfo, new ResolverListAdapter.ViewHolder(icon)); + iconTask.execute(); ((TextView) findViewById(R.id.open_cross_profile)).setText( getResources().getString( @@ -1479,12 +1498,20 @@ public class ResolverActivity extends Activity implements prepareIntentForCrossProfileLaunch(intent); } safelyStartActivityAsUser(otherProfileResolveInfo, - mMultiProfilePagerAdapter.getInactiveListAdapter().mResolverListController - .getUserHandle()); + inactiveAdapter.mResolverListController.getUserHandle()); }); } + /** + * Mini resolver should be used when all of the following are true: + * 1. This is the intent picker (ResolverActivity). + * 2. This profile only has web browser matches. + * 3. The other profile has a single non-browser match. + */ private boolean shouldUseMiniResolver() { + if (!mIsIntentPicker) { + return false; + } if (mMultiProfilePagerAdapter.getActiveListAdapter() == null || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { return false; @@ -1790,7 +1817,7 @@ public class ResolverActivity extends Activity implements void onHorizontalSwipeStateChanged(int state) {} private void maybeHideDivider() { - if (!isIntentPicker()) { + if (!mIsIntentPicker) { return; } final View divider = findViewById(R.id.divider); @@ -1807,7 +1834,7 @@ public class ResolverActivity extends Activity implements protected void onProfileTabSelected() { } private void resetCheckedItem() { - if (!isIntentPicker()) { + if (!mIsIntentPicker) { return; } mLastSelected = ListView.INVALID_POSITION; diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index e466c8866afa..2e4860a6da26 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -71,6 +71,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; @@ -194,6 +195,7 @@ public class InteractionJankMonitor { public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; public static final int CUJ_SETTINGS_SLIDER = 53; + public static final int CUJ_TAKE_SCREENSHOT = 54; private static final int NO_STATSD_LOGGING = -1; @@ -256,6 +258,7 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT, }; private static volatile InteractionJankMonitor sInstance; @@ -330,6 +333,7 @@ public class InteractionJankMonitor { CUJ_LOCKSCREEN_LAUNCH_CAMERA, CUJ_SPLIT_SCREEN_RESIZE, CUJ_SETTINGS_SLIDER, + CUJ_TAKE_SCREENSHOT, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -756,6 +760,8 @@ public class InteractionJankMonitor { return "CUJ_SPLIT_SCREEN_RESIZE"; case CUJ_SETTINGS_SLIDER: return "SETTINGS_SLIDER"; + case CUJ_TAKE_SCREENSHOT: + return "TAKE_SCREENSHOT"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java index 3eb980465214..5adaf4fbd2cc 100644 --- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java +++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java @@ -28,18 +28,15 @@ import com.android.internal.R; public final class NotificationAccessConfirmationActivityContract { public static final String EXTRA_USER_ID = "user_id"; public static final String EXTRA_COMPONENT_NAME = "component_name"; - public static final String EXTRA_PACKAGE_TITLE = "package_title"; /** * Creates a launcher intent for NotificationAccessConfirmationActivity. */ - public static Intent launcherIntent(Context context, int userId, ComponentName component, - String packageTitle) { + public static Intent launcherIntent(Context context, int userId, ComponentName component) { return new Intent() .setComponent(ComponentName.unflattenFromString(context.getString( R.string.config_notificationAccessConfirmationActivity))) .putExtra(EXTRA_USER_ID, userId) - .putExtra(EXTRA_COMPONENT_NAME, component) - .putExtra(EXTRA_PACKAGE_TITLE, packageTitle); + .putExtra(EXTRA_COMPONENT_NAME, component); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 3f87de2e0f8a..b03a8cbeb79c 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -167,7 +167,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 207; + static final int VERSION = 208; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -3981,8 +3981,7 @@ public class BatteryStatsImpl extends BatteryStats { if (idxObj != null) { idx = idxObj; if ((idx & TAG_FIRST_OCCURRENCE_FLAG) != 0) { - idx &= ~TAG_FIRST_OCCURRENCE_FLAG; - mHistoryTagPool.put(tag, idx); + mHistoryTagPool.put(tag, idx & ~TAG_FIRST_OCCURRENCE_FLAG); } return idx; } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) { diff --git a/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java b/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java index fc064ea1ff10..4173abff9042 100644 --- a/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java +++ b/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java @@ -16,13 +16,18 @@ package com.android.internal.policy; +import android.annotation.NonNull; +import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; +import java.util.Collection; + /** * A ContentObserver for listening {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE} setting key. * @@ -59,7 +64,11 @@ public class ForceShowNavBarSettingsObserver extends ContentObserver { } @Override - public void onChange(boolean selfChange) { + public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags, int userId) { + if (userId != ActivityManager.getCurrentUser()) { + return; + } + if (mOnChangeRunnable != null) { mOnChangeRunnable.run(); } diff --git a/core/java/com/android/internal/widget/PasswordValidationError.java b/core/java/com/android/internal/widget/PasswordValidationError.java index 41b234ef024e..f678b130f4f1 100644 --- a/core/java/com/android/internal/widget/PasswordValidationError.java +++ b/core/java/com/android/internal/widget/PasswordValidationError.java @@ -24,16 +24,17 @@ public class PasswordValidationError { public static final int WEAK_CREDENTIAL_TYPE = 1; public static final int CONTAINS_INVALID_CHARACTERS = 2; public static final int TOO_SHORT = 3; - public static final int TOO_LONG = 4; - public static final int CONTAINS_SEQUENCE = 5; - public static final int NOT_ENOUGH_LETTERS = 6; - public static final int NOT_ENOUGH_UPPER_CASE = 7; - public static final int NOT_ENOUGH_LOWER_CASE = 8; - public static final int NOT_ENOUGH_DIGITS = 9; - public static final int NOT_ENOUGH_SYMBOLS = 10; - public static final int NOT_ENOUGH_NON_LETTER = 11; - public static final int NOT_ENOUGH_NON_DIGITS = 12; - public static final int RECENTLY_USED = 13; + public static final int TOO_SHORT_WHEN_ALL_NUMERIC = 4; + public static final int TOO_LONG = 5; + public static final int CONTAINS_SEQUENCE = 6; + public static final int NOT_ENOUGH_LETTERS = 7; + public static final int NOT_ENOUGH_UPPER_CASE = 8; + public static final int NOT_ENOUGH_LOWER_CASE = 9; + public static final int NOT_ENOUGH_DIGITS = 10; + public static final int NOT_ENOUGH_SYMBOLS = 11; + public static final int NOT_ENOUGH_NON_LETTER = 12; + public static final int NOT_ENOUGH_NON_DIGITS = 13; + public static final int RECENTLY_USED = 14; // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings. public final int errorCode; @@ -61,6 +62,7 @@ public class PasswordValidationError { case WEAK_CREDENTIAL_TYPE: return "Weak credential type"; case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character"; case TOO_SHORT: return "Password too short"; + case TOO_SHORT_WHEN_ALL_NUMERIC: return "Password too short"; case TOO_LONG: return "Password too long"; case CONTAINS_SEQUENCE: return "Sequence too long"; case NOT_ENOUGH_LETTERS: return "Too few letters"; diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java index f7af67b3b2a8..c484525dbe20 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java @@ -21,7 +21,6 @@ import android.content.Context; import android.graphics.Rect; import android.view.MenuItem; import android.view.View; -import android.view.selectiontoolbar.SelectionToolbarManager; import android.widget.PopupWindow; import java.util.List; @@ -93,10 +92,7 @@ public interface FloatingToolbarPopup { * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation. */ static FloatingToolbarPopup createInstance(Context context, View parent) { - boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context); - return enabled - ? new RemoteFloatingToolbarPopup(context, parent) - : new LocalFloatingToolbarPopup(context, parent); + return new LocalFloatingToolbarPopup(context, parent); } } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index c769da57eecc..4044c579a57f 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1921,6 +1921,7 @@ static void nativeRemoveCurrentInputFocus(JNIEnv* env, jclass clazz, jlong trans FocusRequest request; request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); request.displayId = displayId; + request.windowName = "<null>"; transaction->setFocusedWindow(request); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0f328b034f38..f20b824e6bf7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6441,11 +6441,11 @@ <!-- @SystemApi Must be required by a safety source to send an update using the {@link android.safetycenter.SafetyCenterManager}. - <p>Protection level: signature|privileged + <p>Protection level: internal|privileged @hide --> <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="internal|privileged" /> <!-- @SystemApi Allows an application to launch device manager setup screens. <p>Not for use by third-party applications. diff --git a/core/res/res/anim-ldrtl/task_fragment_close_enter.xml b/core/res/res/anim-ldrtl/task_fragment_close_enter.xml new file mode 100644 index 000000000000..e5f5707c9a20 --- /dev/null +++ b/core/res/res/anim-ldrtl/task_fragment_close_enter.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + + <alpha + android:fromAlpha="1.0" + android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/linear" + android:startOffset="0" + android:duration="200" /> + + <translate + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:startOffset="0" + android:duration="200" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml new file mode 100644 index 000000000000..c5a36542cc7d --- /dev/null +++ b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + + <alpha + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/linear" + android:startOffset="35" + android:duration="83" /> + + <translate + android:fromXDelta="0" + android:toXDelta="-10%" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:startOffset="0" + android:duration="200" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim-ldrtl/task_fragment_open_enter.xml b/core/res/res/anim-ldrtl/task_fragment_open_enter.xml new file mode 100644 index 000000000000..b6f1af3e7840 --- /dev/null +++ b/core/res/res/anim-ldrtl/task_fragment_open_enter.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +/* +** Copyright 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. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + + <alpha + android:fromAlpha="0" + android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/linear" + android:startOffset="50" + android:duration="83" /> + + <translate + android:fromXDelta="-10%" + android:toXDelta="0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="450" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim-ldrtl/task_fragment_open_exit.xml b/core/res/res/anim-ldrtl/task_fragment_open_exit.xml new file mode 100644 index 000000000000..6cea53c3e934 --- /dev/null +++ b/core/res/res/anim-ldrtl/task_fragment_open_exit.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +/* +** Copyright 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. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + + <alpha + android:fromAlpha="1.0" + android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/standard_accelerate" + android:startOffset="0" + android:duration="450" /> + + <translate + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="450" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/task_fragment_close_enter.xml b/core/res/res/anim/task_fragment_close_enter.xml index c940552d53ad..cb6cdbeb71f4 100644 --- a/core/res/res/anim/task_fragment_close_enter.xml +++ b/core/res/res/anim/task_fragment_close_enter.xml @@ -17,16 +17,21 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> - <scale - android:fromXScale="1.1" - android:toXScale="1" - android:fromYScale="1.1" - android:toYScale="1" - android:pivotX="50%" - android:pivotY="50%" + <alpha + android:fromAlpha="1.0" + android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/linear" + android:startOffset="0" + android:duration="200" /> + + <translate android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> + android:startOffset="0" + android:duration="200" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/task_fragment_close_exit.xml b/core/res/res/anim/task_fragment_close_exit.xml index 8998f764ff9b..84d8b7e6b5cd 100644 --- a/core/res/res/anim/task_fragment_close_exit.xml +++ b/core/res/res/anim/task_fragment_close_exit.xml @@ -19,24 +19,21 @@ android:shareInterpolator="false" android:zAdjustment="top"> <alpha - android:fromAlpha="1" + android:fromAlpha="1.0" android:toAlpha="0.0" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/linear" - android:startOffset="33" - android:duration="50"/> - <scale - android:fromXScale="1" - android:toXScale="0.9" - android:fromYScale="1" - android:toYScale="0.9" - android:pivotX="50%" - android:pivotY="50%" + android:startOffset="0" + android:duration="83" /> + + <translate + android:fromXDelta="0" + android:toXDelta="10%" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> + android:duration="200" /> </set> diff --git a/core/res/res/anim/task_fragment_open_enter.xml b/core/res/res/anim/task_fragment_open_enter.xml index 6bc47deb2de4..aa61e6f17070 100644 --- a/core/res/res/anim/task_fragment_open_enter.xml +++ b/core/res/res/anim/task_fragment_open_enter.xml @@ -25,17 +25,13 @@ android:fillAfter="true" android:interpolator="@interpolator/linear" android:startOffset="50" - android:duration="50"/> - <scale - android:fromXScale="0.85" - android:toXScale="1" - android:fromYScale="0.85" - android:toYScale="1" - android:pivotX="50%" - android:pivotY="50%" + android:duration="83" /> + <translate + android:fromXDelta="10%" + android:toXDelta="0" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> + android:duration="400" /> </set> diff --git a/core/res/res/anim/task_fragment_open_exit.xml b/core/res/res/anim/task_fragment_open_exit.xml index 160eb84223da..b4914d21d097 100644 --- a/core/res/res/anim/task_fragment_open_exit.xml +++ b/core/res/res/anim/task_fragment_open_exit.xml @@ -17,16 +17,20 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> - <scale - android:fromXScale="1" - android:toXScale="1.05" - android:fromYScale="1" - android:toYScale="1.05" - android:pivotX="50%" - android:pivotY="50%" + <alpha + android:fromAlpha="1.0" + android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" + android:fillAfter="true" + android:interpolator="@interpolator/standard_accelerate" + android:startOffset="0" + android:duration="400" /> + + <translate android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> + android:duration="400" /> </set>
\ No newline at end of file diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml index 252f59e5ac62..0b6a2016f03d 100644 --- a/core/res/res/layout/autofill_fill_dialog.xml +++ b/core/res/res/layout/autofill_fill_dialog.xml @@ -50,6 +50,7 @@ android:visibility="gone" /> </LinearLayout> + <!-- For Authentication. --> <LinearLayout android:id="@+id/autofill_dialog_container" android:layout_width="fill_parent" @@ -58,7 +59,7 @@ android:paddingStart="@dimen/autofill_save_inner_padding" android:paddingEnd="@dimen/autofill_save_inner_padding" android:visibility="gone" - style="@style/AutofillDatasetPicker" /> + android:background="@drawable/autofill_dataset_picker_background"/> <ListView android:id="@+id/autofill_dialog_list" @@ -68,8 +69,8 @@ android:drawSelectorOnTop="true" android:clickable="true" android:divider="?android:attr/listDivider" - android:visibility="gone" - style="@style/AutofillDatasetPicker" /> + android:background="@drawable/autofill_dataset_picker_background" + android:visibility="gone"/> <com.android.internal.widget.ButtonBarLayout android:layout_width="match_parent" diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index 44ed6f2a0676..85fe2835fd5e 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -24,12 +24,11 @@ android:id="@id/contentPanel"> <RelativeLayout - android:id="@+id/title_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" android:elevation="@dimen/resolver_elevation" - android:paddingTop="@dimen/resolver_small_margin" + android:paddingTop="24dp" android:paddingStart="@dimen/resolver_edge_margin" android:paddingEnd="@dimen/resolver_edge_margin" android:paddingBottom="@dimen/resolver_title_padding_bottom" @@ -47,8 +46,12 @@ android:id="@+id/open_cross_profile" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingTop="16dp" android:layout_below="@id/icon" android:layout_centerHorizontal="true" + android:textSize="24sp" + android:lineHeight="32sp" + android:gravity="center" android:textColor="?android:textColorPrimary" /> </RelativeLayout> @@ -58,15 +61,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" + android:paddingTop="32dp" + android:paddingBottom="@dimen/resolver_button_bar_spacing" android:orientation="vertical" android:background="?attr/colorBackground" android:layout_ignoreOffset="true"> - <View - android:id="@+id/resolver_button_bar_divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:background="?attr/colorBackground" - android:foreground="?attr/dividerVertical" /> <RelativeLayout style="?attr/buttonBarStyle" android:layout_width="match_parent" @@ -77,7 +76,6 @@ android:orientation="horizontal" android:layoutDirection="locale" android:measureWithLargestChild="true" - android:paddingTop="@dimen/resolver_button_bar_spacing" android:paddingBottom="@dimen/resolver_button_bar_spacing" android:paddingStart="@dimen/resolver_edge_margin" android:paddingEnd="@dimen/resolver_small_margin" diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 6242a3d3b755..abe40f30abec 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1680,7 +1680,7 @@ <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"FRA"</string> <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Vil du give <xliff:g id="SERVICE">%1$s</xliff:g> fuld kontrol over din enhed?"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"Fuld kontrol er velegnet til apps, der hjælper dig med hjælpefunktioner, men ikke de fleste apps."</string> - <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og styre skærm"</string> + <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og styre skærmen"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Den kan læse alt indhold på skærmen og vise indhold oven på andre apps."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Se og udføre handlinger"</string> <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore dine interaktioner med en app eller en hardwaresensor og interagere med apps på dine vegne."</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 18b3bf61107f..1c23424e1f93 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1387,7 +1387,7 @@ <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"Bistaratu beste aplikazioen gainean"</string> <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"<xliff:g id="NAME">%s</xliff:g> aplikazioen gainean agertzea"</string> <string name="alert_windows_notification_title" msgid="6331662751095228536">"Besteen gainean agertzen da <xliff:g id="NAME">%s</xliff:g>"</string> - <string name="alert_windows_notification_message" msgid="6538171456970725333">"Ez baduzu nahi <xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string> + <string name="alert_windows_notification_message" msgid="6538171456970725333">"<xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea nahi ez baduzu, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string> <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Desaktibatu"</string> <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"<xliff:g id="NAME">%s</xliff:g> egiaztatzen…"</string> <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Edukia berrikusten"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 1e0851b62719..4a904cf9c52f 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1312,7 +1312,7 @@ <string name="select_character" msgid="3352797107930786979">"درج نویسه"</string> <string name="sms_control_title" msgid="4748684259903148341">"درحال ارسال پیامکها"</string> <string name="sms_control_message" msgid="6574313876316388239">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> درحال ارسال تعداد زیادی پیامک است. آیا اجازه میدهید این برنامه همچنان پیامک ارسال کند؟"</string> - <string name="sms_control_yes" msgid="4858845109269524622">"مجاز است"</string> + <string name="sms_control_yes" msgid="4858845109269524622">"اجازه دادن"</string> <string name="sms_control_no" msgid="4845717880040355570">"مجاز نبودن"</string> <string name="sms_short_code_confirm_message" msgid="1385416688897538724">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> مایل است پیامی به <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b> ارسال کند."</string> <string name="sms_short_code_details" msgid="2723725738333388351">"این مورد "<b>"شاید هزینهای"</b>" را به حساب دستگاه همراهتان بگذارد."</string> @@ -1684,7 +1684,7 @@ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"میتواند همه محتوای صفحه را بخواند و آن را روی بقیه برنامهها نمایش دهد."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"مشاهده و انجام کنشها"</string> <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"این عملکرد میتواند با برنامه یا حسگری سختافزاری تعاملاتتان را ردیابی کند و ازطرف شما با برنامهها تعامل داشته باشد."</string> - <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"مجاز"</string> + <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازه دادن"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مجاز نبودن"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"برای استفاده از ویژگی، روی آن ضربه بزنید:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"انتخاب ویژگیهای موردنظر برای استفاده با دکمه دسترسپذیری"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 9a65373fbc37..fe5c92c54f55 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -2261,7 +2261,7 @@ <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Novas opcións de configuración de ampliación"</string> <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Xa podes ampliar parte da pantalla"</string> <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activar en Configuración"</string> - <string name="dismiss_action" msgid="1728820550388704784">"Ignorar"</string> + <string name="dismiss_action" msgid="1728820550388704784">"Pechar"</string> <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Desbloquea o micrófono do dispositivo"</string> <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Desbloquea a cámara do dispositivo"</string> <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Para <b><xliff:g id="APP">%s</xliff:g></b> e todas as aplicacións e servizos"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 88b692e48e2b..6f214a8e0c16 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -245,10 +245,10 @@ <string name="global_action_power_options" msgid="1185286119330160073">"Սնուցման կոճակ"</string> <string name="global_action_restart" msgid="4678451019561687074">"Վերագործարկել"</string> <string name="global_action_emergency" msgid="1387617624177105088">"Շտապ կանչ"</string> - <string name="global_action_bug_report" msgid="5127867163044170003">"Վրիպակի զեկույց"</string> + <string name="global_action_bug_report" msgid="5127867163044170003">"Հաղորդում վրիպակի մասին"</string> <string name="global_action_logout" msgid="6093581310002476511">"Ավարտել աշխատաշրջանը"</string> <string name="global_action_screenshot" msgid="2610053466156478564">"Սքրինշոթ"</string> - <string name="bugreport_title" msgid="8549990811777373050">"Հաշվետվություն վրիպակի մասին"</string> + <string name="bugreport_title" msgid="8549990811777373050">"Հաղորդում վրիպակի մասին"</string> <string name="bugreport_message" msgid="5212529146119624326">"Սա տեղեկություններ կհավաքագրի ձեր սարքի առկա կարգավիճակի մասին և կուղարկի այն էլեկտրոնային նամակով: Որոշակի ժամանակ կպահանջվի վրիպակի մասին զեկուցելու պահից սկսած մինչ ուղարկելը: Խնդրում ենք փոքր-ինչ համբերատար լինել:"</string> <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Ինտերակտիվ զեկույց"</string> <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Հիմնականում օգտագործեք այս տարբերակը: Այն ձեզ թույլ է տալիս հետևել զեկույցի ստեղծման գործընթացին, խնդրի մասին լրացուցիչ տեղեկություններ մուտքագրել և սքրինշոթներ ստեղծել: Կարող է բաց թողնել քիչ օգտագործվող որոշ բաժիններ, որոնց ստեղծումը երկար է տևում:"</string> @@ -1372,7 +1372,7 @@ <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"USB միացքը կարող է օգտագործվել"</string> <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"Հեռախոսում ջուր կամ աղտ չի հայտնաբերվել:"</string> <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Վրիպակի զեկույցի ստեղծում…"</string> - <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Տրամադրե՞լ վրիպակի զեկույցը:"</string> + <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Կիսվե՞լ վրիպակի մասին հաղորդմամբ"</string> <string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"Վրիպակի զեկույցի տրամադրում…"</string> <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Այս սարքի անսարքությունների վերացման նպատակով ձեր ադմինիստրատորին անհրաժեշտ է վրիպակի հաշվետվություն: Կարող են տրամադրվել տեղեկություններ հավելվածների մասին և այլ տվյալներ։"</string> <string name="share_remote_bugreport_action" msgid="7630880678785123682">"ՏՐԱՄԱԴՐԵԼ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 6d26efd33afe..0a5b9b601e9e 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1696,7 +1696,7 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"단축키 사용"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"색상 반전"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"색상 보정"</string> - <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"한 손 사용 모드"</string> + <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"한 손 모드"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"더 어둡게"</string> <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"볼륨 키를 길게 눌렀습니다. <xliff:g id="SERVICE_NAME">%1$s</xliff:g>이(가) 사용 설정되었습니다."</string> <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"볼륨 키를 길게 눌렀습니다. <xliff:g id="SERVICE_NAME">%1$s</xliff:g>이(가) 사용 중지되었습니다."</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index f7c4ca3bc1e2..d86371b5c3d8 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -221,7 +221,7 @@ <string name="silent_mode_silent" msgid="5079789070221150912">"Коңгуроо өчүк"</string> <string name="silent_mode_vibrate" msgid="8821830448369552678">"Чалганда титирөө"</string> <string name="silent_mode_ring" msgid="6039011004781526678">"Коңгуроо жандырылган"</string> - <string name="reboot_to_update_title" msgid="2125818841916373708">"Android тутум жаңыртуусу"</string> + <string name="reboot_to_update_title" msgid="2125818841916373708">"Android системасын жаңыртуу"</string> <string name="reboot_to_update_prepare" msgid="6978842143587422365">"Жаңыртууга даярдалууда…"</string> <string name="reboot_to_update_package" msgid="4644104795527534811">"Жаңыртуу топтому иштелүүдө…"</string> <string name="reboot_to_update_reboot" msgid="4474726009984452312">"Өчүрүлүп күйгүзүлүүдө…"</string> @@ -1670,10 +1670,10 @@ <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең 3 секунддай коё бербей басып туруңуз."</string> <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Атайын мүмкүнчүлүктөрдүн ыкчам баскычын иштетесизби?"</string> - <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string> + <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ыкчам баскычын иштетесизби?"</string> - <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string> + <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ооба"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Жок"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"КҮЙҮК"</string> @@ -1681,7 +1681,7 @@ <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматына түзмөгүңүздү толугу менен көзөмөлдөөгө уруксат бересизби?"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"Толук көзөмөл атайын мүмкүнчүлүктөрдү пайдаланууга керек, бирок калган көпчүлүк колдонмолорго кереги жок."</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Экранды көрүп, көзөмөлдөө"</string> - <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Кызмат экрандагы нерселерди окуп, материалды башка колдонмолордун үстүнөн көрсөтөт."</string> + <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Кызмат экрандагы нерселерди окуп, аларды башка колдонмолордун үстүнөн көрсөтөт."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Аракеттерди көрүп, аткаруу"</string> <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Кызмат колдонмодо жасаган аракеттериңизге же түзмөктүн сенсорлоруна көз салып, сиздин атыңыздан буйруктарды берет."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Уруксат берүү"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 7dbf02abc84e..bcb06b9985de 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1681,7 +1681,7 @@ <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> എന്നതിന് നിങ്ങളുടെ ഉപകരണത്തിന്മേൽ പൂർണ്ണ നിയന്ത്രണം അനുവദിക്കണോ?"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"ഉപയോഗസഹായി ആവശ്യങ്ങൾക്കായി നിങ്ങളെ സഹായിക്കുന്ന ആപ്പുകൾക്ക് പൂർണ്ണ നിയന്ത്രണം അനുയോജ്യമാണെങ്കിലും മിക്ക ആപ്പുകൾക്കും അനുയോജ്യമല്ല."</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"സ്ക്രീൻ കാണുക, നിയന്ത്രിക്കുക"</string> - <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"ഇതിന് സ്ക്രീനിലെ എല്ലാ ഉള്ളടക്കവും വായിക്കാനും മറ്റ് ആപ്പുകളിൽ ഉള്ളടക്കം പ്രദർശിപ്പിക്കാനുമാവും."</string> + <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"ഇതിന് സ്ക്രീനിലെ എല്ലാ ഉള്ളടക്കവും വായിക്കാനും മറ്റ് ആപ്പുകൾക്ക് മുകളിൽ ഉള്ളടക്കം പ്രദർശിപ്പിക്കാനുമാകും."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"കാണുക, പ്രവർത്തനങ്ങൾ നിർവഹിക്കുക"</string> <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ഇതിന് ഒരു ആപ്പുമായോ ഹാർഡ്വെയർ സെൻസറുമായോ ഉള്ള നിങ്ങളുടെ ആശയവിനിമയങ്ങൾ ട്രാക്ക് ചെയ്യാനും നിങ്ങളുടെ പേരിൽ ആശയവിനിമയം നടത്താനും കഴിയും."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"അനുവദിക്കൂ"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 8325473ecdb8..e4b034d1227a 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -103,7 +103,7 @@ <string name="serviceClassVoice" msgid="2065556932043454987">"व्हॉइस"</string> <string name="serviceClassData" msgid="4148080018967300248">"डेटा"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"फॅक्स"</string> - <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> + <string name="serviceClassSMS" msgid="1547664561704509004">"एसएमएस"</string> <string name="serviceClassDataAsync" msgid="2029856900898545984">"असंकालिक"</string> <string name="serviceClassDataSync" msgid="7895071363569133704">"सिंक करा"</string> <string name="serviceClassPacket" msgid="1430642951399303804">"पॅकेट"</string> @@ -303,7 +303,7 @@ <string name="permgroupdesc_location" msgid="1995955142118450685">"या डिव्हाइसच्या स्थानावर प्रवेश"</string> <string name="permgrouplab_calendar" msgid="6426860926123033230">"कॅलेंडर"</string> <string name="permgroupdesc_calendar" msgid="6762751063361489379">"आपल्या कॅलेंडरवर प्रवेश"</string> - <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string> + <string name="permgrouplab_sms" msgid="795737735126084874">"एसएमएस"</string> <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS मेसेज पाठवणे आणि पाहणे हे"</string> <string name="permgrouplab_storage" msgid="5570124978732352858">"फाइल आणि दस्तऐवज"</string> <string name="permgroupdesc_storage" msgid="8352226729501080525">"तुमच्या डिव्हाइसवर फाइल आणि दस्तऐवज अॅक्सेस करा"</string> @@ -1683,7 +1683,7 @@ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"स्क्रीन पहा आणि नियंत्रित करा"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"ते स्क्रीनवरील सर्व आशय वाचू शकते आणि इतर ॲप्सवर आशय प्रदर्शित करू शकते."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"पहा आणि क्रिया करा"</string> - <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"तुम्ही ॲप किंवा हार्डवेअर सेन्सर कसे वापरता याचा हे मागोवा घेऊ शकते आणि इतर ॲप्ससोबत तुमच्या वतीने काम करू शकते."</string> + <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"हे तुम्ही ॲप किंवा हार्डवेअर सेन्सर कसे वापरता ते ट्रॅक करू शकते आणि इतर ॲप्ससोबत तुमच्या वतीने संवाद साधू शकते."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमती द्या"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"नकार द्या"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"वैशिष्ट्य वापरणे सुरू करण्यासाठी त्यावर टॅप करा:"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index fc1495d78202..e1a4d6c2598d 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1681,7 +1681,7 @@ <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> ကို သင့်စက်အား အပြည့်အဝထိန်းချုပ်ခွင့် ပေးလိုပါသလား။"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"အများသုံးစွဲနိုင်မှု လိုအပ်ချက်များအတွက် အထောက်အကူပြုသည့် အက်ပ်များအား အပြည့်အဝ ထိန်းချုပ်ခွင့်ပေးခြင်းသည် သင့်လျော်သော်လည်း အက်ပ်အများစုအတွက် မသင့်လျော်ပါ။"</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"ဖန်သားပြင်ကို ကြည့်ရှုထိန်းချုပ်ခြင်း"</string> - <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"၎င်းသည် မျက်နှာပြင်ပေါ်ရှိ အကြောင်းအရာများအားလုံးကို ဖတ်နိုင်ပြီး အခြားအက်ပ်များအပေါ်တွင် ထိုအကြောင်းအရာကို ဖော်ပြနိုင်သည်။"</string> + <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"၎င်းသည် မျက်နှာပြင်ပေါ်ရှိ အကြောင်းအရာအားလုံးကို ဖတ်နိုင်ပြီး အခြားအက်ပ်များအပေါ်တွင် အကြောင်းအရာကို ဖော်ပြနိုင်သည်။"</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"လုပ်ဆောင်ချက်များကို ကြည့်ရှုဆောင်ရွက်ခြင်း"</string> <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"၎င်းသည် အက်ပ်တစ်ခု သို့မဟုတ် အာရုံခံကိရိယာကို အသုံးပြု၍ သင့်ပြန်လှန်တုံ့ပြန်မှုများကို မှတ်သားနိုင်ပြီး သင့်ကိုယ်စား အက်ပ်များနှင့် ပြန်လှန်တုံ့ပြန်နိုင်သည်။"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ခွင့်ပြုရန်"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 5fff6918224c..01fed174df8a 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1679,9 +1679,9 @@ <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"AAN"</string> <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"UIT"</string> <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Toestaan dat <xliff:g id="SERVICE">%1$s</xliff:g> volledige controle over je apparaat heeft?"</string> - <string name="accessibility_service_warning_description" msgid="291674995220940133">"Volledige controle is gepast voor apps die je helpen met toegankelijkheid, maar voor de meeste apps is het ongepast."</string> + <string name="accessibility_service_warning_description" msgid="291674995220940133">"Volledige controle is gepast voor apps die je helpen met toegankelijkheid, maar niet voor de meeste apps."</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Scherm bekijken en bedienen"</string> - <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"De functie kan alle content op het scherm lezen en content bovenop andere apps weergeven"</string> + <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"De functie kan alle content op het scherm lezen en content bovenop andere apps weergeven."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Acties bekijken en uitvoeren"</string> <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"De functie kan je interacties met een app of een hardwaresensor bijhouden en namens jou met apps communiceren."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Toestaan"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index a666c8178d93..b43745eb5e59 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -351,7 +351,7 @@ <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Aplikaciji omogoča razširjanje ali strnjevanje vrstice stanja."</string> <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"Prikaz obvestil kot celozaslonskih dejavnosti v zaklenjeni napravi"</string> <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Aplikaciji dovoli, da prikaže obvestila kot celozaslonske dejavnosti v zaklenjeni napravi."</string> - <string name="permlab_install_shortcut" msgid="7451554307502256221">"nameščanje bližnjic"</string> + <string name="permlab_install_shortcut" msgid="7451554307502256221">"Nameščanje bližnjic"</string> <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Aplikaciji omogoča dodajanje bližnjic na začetni zaslon brez posredovanja uporabnika."</string> <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"odstranjevanje bližnjic"</string> <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Aplikaciji omogoča odstranjevanje bližnjic z začetnega zaslona brez posredovanja uporabnika."</string> @@ -1699,7 +1699,7 @@ <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija barv"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Popravljanje barv"</string> <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enoročni način"</string> - <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Zelo zatemnjeno"</string> + <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Zelo zatemnjen zaslon"</string> <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Tipki za glasnost sta pridržani. Storitev <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je vklopljena."</string> <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Tipki za glasnost sta pridržani. Storitev <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je izklopljena."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Za uporabo storitve <xliff:g id="SERVICE_NAME">%1$s</xliff:g> pritisnite obe tipki za glasnost in ju pridržite tri sekunde"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index dcf9fcd9aea8..044264bdb2ff 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1679,7 +1679,7 @@ <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"เปิด"</string> <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"ปิด"</string> <string name="accessibility_enable_service_title" msgid="3931558336268541484">"อนุญาตให้ <xliff:g id="SERVICE">%1$s</xliff:g> ควบคุมอุปกรณ์อย่างเต็มที่ไหม"</string> - <string name="accessibility_service_warning_description" msgid="291674995220940133">"การควบคุมอย่างเต็มที่เหมาะสำหรับแอปที่ช่วยคุณในเรื่องความต้องการความช่วยเหลือพิเศษแต่ไม่เหมาะสำหรับแอปส่วนใหญ่"</string> + <string name="accessibility_service_warning_description" msgid="291674995220940133">"การควบคุมอย่างเต็มที่เหมาะสำหรับแอปเกี่ยวกับความช่วยเหลือพิเศษ แต่ไม่เหมาะสำหรับแอปส่วนใหญ่"</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"ดูและควบคุมหน้าจอ"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"การควบคุมนี้สามารถอ่านเนื้อหาทั้งหมดบนหน้าจอและแสดงเนื้อหาทับแอปอื่นๆ"</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"ดูและดำเนินการ"</string> @@ -1712,7 +1712,7 @@ <string name="user_switching_message" msgid="1912993630661332336">"กำลังเปลี่ยนเป็น <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"กำลังออกจากระบบ <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"เจ้าของ"</string> - <string name="guest_name" msgid="8502103277839834324">"ผู้มาเยือน"</string> + <string name="guest_name" msgid="8502103277839834324">"ผู้ใช้ชั่วคราว"</string> <string name="error_message_title" msgid="4082495589294631966">"ข้อผิดพลาด"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"ผู้ดูแลระบบไม่อนุญาตให้ทำการเปลี่ยนแปลงนี้"</string> <string name="app_not_found" msgid="3429506115332341800">"ไม่พบแอปพลิเคชันสำหรับการทำงานนี้"</string> @@ -1958,7 +1958,7 @@ <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"เชื่อมต่อ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g> แล้ว"</string> <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"แตะเพื่อดูไฟล์"</string> <string name="pin_target" msgid="8036028973110156895">"ปักหมุด"</string> - <string name="pin_specific_target" msgid="7824671240625957415">"ตรึง <xliff:g id="LABEL">%1$s</xliff:g>"</string> + <string name="pin_specific_target" msgid="7824671240625957415">"ปักหมุด <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="unpin_target" msgid="3963318576590204447">"เลิกปักหมุด"</string> <string name="unpin_specific_target" msgid="3859828252160908146">"เลิกปักหมุด <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="app_info" msgid="6113278084877079851">"ข้อมูลแอป"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 5ea95e932371..394e90cefe82 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1681,9 +1681,9 @@ <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> کو آپ کے آلے کا مکمل کنٹرول حاصل کرنے کی اجازت دیں؟"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"مکمل کنٹرول ان ایپس کے لیے مناسب ہے جو ایکسیسبیلٹی کی ضروریات میں آپ کی مدد کرتی ہیں، لیکن زیادہ تر ایپس کیلئے مناسب نہیں۔"</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"اسکرین کو دیکھیں اور کنٹرول کریں"</string> - <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"یہ تمام مواد کو اسکرین پر پڑھ اور دیگر ایپس پر مواد کو ڈسپلے کر سکتا ہے۔"</string> + <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"یہ اسکرین پر موجود تمام مواد کو پڑھ سکتا ہے اور دیگر ایپس پر مواد کو ڈسپلے کر سکتا ہے۔"</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"کارروائیاں دیکھیں اور انجام دیں"</string> - <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"یہ آپ کے تعاملات کو ایپ یا ہارڈویئر سینسر کے ذریعے ٹریک کر سکتا ہے، اور آپ کی طرف سے ایپ کے ساتھ تعمل کر سکتا ہے۔"</string> + <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"یہ کسی ایپ یا ہارڈویئر سینسر کے ساتھ آپ کے تعاملات کو ٹریک کر سکتا ہے، اور آپ کی طرف سے ایپس کے ساتھ تعامل کر سکتا ہے۔"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازت دیں"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مسترد کریں"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ایک خصوصیت کا استعمال شروع کرنے کیلئے اسے تھپتھپائیں:"</string> @@ -1697,7 +1697,7 @@ <string name="color_inversion_feature_name" msgid="326050048927789012">"رنگوں کی تقلیب"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"رنگ کی تصحیح"</string> <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ایک ہاتھ کی وضع"</string> - <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"اضافی دھندلا"</string> + <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"اضافی مدھم"</string> <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"والیوم کی کلیدوں کو دبائے رکھا گیا۔ <xliff:g id="SERVICE_NAME">%1$s</xliff:g> آن ہے۔"</string> <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"والیوم کی کلیدوں کو دبائے رکھا گیا۔ <xliff:g id="SERVICE_NAME">%1$s</xliff:g> آف ہے۔"</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> کا استعمال کرنے کے لیے 3 سیکنڈ تک والیوم کی دونوں کلیدوں کو چھوئیں اور دبائے رکھیں"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 178a1298fe0f..0db56e3c2e13 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -581,7 +581,7 @@ <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"未能識別"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"已取消驗證"</string> - <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN、圖形或密碼"</string> + <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN、圖案或密碼"</string> <string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string> <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定"</string> <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定解鎖憑證"</string> @@ -776,7 +776,7 @@ <string name="policylab_setGlobalProxy" msgid="215332221188670221">"設定裝置的全域代理伺服器"</string> <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"設定政策啟用時所要使用的裝置全域代理伺服器,只有裝置擁有者可以設定全域代理伺服器。"</string> <string name="policylab_expirePassword" msgid="6015404400532459169">"設定螢幕鎖定密碼期限"</string> - <string name="policydesc_expirePassword" msgid="9136524319325960675">"變更螢幕鎖定密碼、PIN 或圖形的更改頻率。"</string> + <string name="policydesc_expirePassword" msgid="9136524319325960675">"變更螢幕鎖定密碼、PIN 或圖案的更改頻率。"</string> <string name="policylab_encryptedStorage" msgid="9012936958126670110">"設定儲存裝置加密"</string> <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"必須為儲存的應用程式資料加密。"</string> <string name="policylab_disableCamera" msgid="5749486347810162018">"停用相機"</string> @@ -911,7 +911,7 @@ <string name="lockscreen_screen_locked" msgid="7364905540516041817">"螢幕已鎖定。"</string> <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"按選單鍵解鎖或撥打緊急電話。"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"按選單鍵解鎖。"</string> - <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"畫出解鎖圖形來為螢幕解鎖"</string> + <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"畫出解鎖圖案來為螢幕解鎖"</string> <string name="lockscreen_emergency_call" msgid="7500692654885445299">"緊急電話"</string> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"返回通話"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"正確!"</string> @@ -940,12 +940,12 @@ <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"請參閱使用者指南或與客戶服務中心聯絡。"</string> <string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"SIM 卡處於鎖定狀態。"</string> <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"正在解除 SIM 卡鎖定..."</string> - <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> + <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"您已輸入錯誤的密碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"您已輸入錯誤的 PIN 碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用 Google 登入資料將 Android TV 裝置解鎖。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用 Google 登入資料將 Android TV 裝置解鎖。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用您的 Google 登入資料解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"您嘗試解除這部平板電腦的鎖定已失敗 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,剩餘 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次嘗試機會。如果失敗次數超過嘗試次數限制,平板電腦將恢復原廠設定,所有使用者資料均會遺失。"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"您已 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次無法解鎖 Android TV 裝置。如果再失敗 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次,Android TV 裝置將回復原廠設定,所有使用者資料均會遺失。"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"您嘗試解除這部手機的鎖定已失敗 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,剩餘 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次嘗試機會。如果失敗次數超過嘗試次數限制,手機將恢復原廠設定,所有使用者資料均會遺失。"</string> @@ -953,9 +953,9 @@ <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"您已 <xliff:g id="NUMBER">%d</xliff:g> 次無法解鎖 Android TV 裝置,Android TV 裝置現在將回復原廠設定。"</string> <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"您嘗試解除這部手機的鎖定已失敗 <xliff:g id="NUMBER">%d</xliff:g> 次。手機現在會重設為原廠預設值。"</string> <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string> - <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"忘記圖形?"</string> + <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"忘記圖案?"</string> <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"帳戶解鎖"</string> - <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"圖形嘗試次數過多"</string> + <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"圖案嘗試次數過多"</string> <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"如要解鎖,請以 Google 帳戶登入。"</string> <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"使用者名稱 (電子郵件)"</string> <string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"密碼"</string> @@ -966,12 +966,12 @@ <string name="lockscreen_unlock_label" msgid="4648257878373307582">"解除鎖定"</string> <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"開啟音效"</string> <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"關閉音效"</string> - <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"已開始繪畫解鎖圖形"</string> - <string name="lockscreen_access_pattern_cleared" msgid="7493849102641167049">"已清除解鎖圖形"</string> + <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"已開始繪畫解鎖圖案"</string> + <string name="lockscreen_access_pattern_cleared" msgid="7493849102641167049">"已清除解鎖圖案"</string> <string name="lockscreen_access_pattern_cell_added" msgid="6746676335293144163">"已加入一格"</string> <string name="lockscreen_access_pattern_cell_added_verbose" msgid="2931364927622563465">"已加入 <xliff:g id="CELL_INDEX">%1$s</xliff:g> 點"</string> - <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"已畫出解鎖圖形"</string> - <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"圖形區域。"</string> + <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"已畫出解鎖圖案"</string> + <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"圖案區域。"</string> <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s。第 %2$d 個小工具,共 %3$d 個。"</string> <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"新增小工具。"</string> <string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"空白"</string> @@ -987,13 +987,13 @@ <string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g>小工具已刪除。"</string> <string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"展開解鎖區域。"</string> <string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"滑動解鎖。"</string> - <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"圖形解鎖。"</string> + <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"圖案解鎖。"</string> <string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"面孔解鎖。"</string> <string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"PIN 解鎖。"</string> <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"SIM 卡 PIN 碼解鎖。"</string> <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"SIM 卡 PUK 解鎖。"</string> <string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"密碼解鎖。"</string> - <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"圖形區域。"</string> + <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"圖案區域。"</string> <string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"滑動區域。"</string> <string name="password_keyboard_label_symbol_key" msgid="2716255580853511949">"?123"</string> <string name="password_keyboard_label_alpha_key" msgid="5294837425652726684">"ABC"</string> @@ -1627,11 +1627,11 @@ <string name="display_manager_overlay_display_name" msgid="5306088205181005861">"重疊效果 #<xliff:g id="ID">%1$d</xliff:g>"</string> <string name="display_manager_overlay_display_title" msgid="1480158037150469170">"<xliff:g id="NAME">%1$s</xliff:g>:<xliff:g id="WIDTH">%2$d</xliff:g>x<xliff:g id="HEIGHT">%3$d</xliff:g>,<xliff:g id="DPI">%4$d</xliff:g> dpi"</string> <string name="display_manager_overlay_display_secure_suffix" msgid="2810034719482834679">"(安全)"</string> - <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"忘記了圖形"</string> - <string name="kg_wrong_pattern" msgid="1342812634464179931">"圖形錯誤"</string> + <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"忘記了圖案"</string> + <string name="kg_wrong_pattern" msgid="1342812634464179931">"圖案錯誤"</string> <string name="kg_wrong_password" msgid="2384677900494439426">"密碼錯誤"</string> <string name="kg_wrong_pin" msgid="3680925703673166482">"PIN 錯誤"</string> - <string name="kg_pattern_instructions" msgid="8366024510502517748">"畫出圖形"</string> + <string name="kg_pattern_instructions" msgid="8366024510502517748">"畫出圖案"</string> <string name="kg_sim_pin_instructions" msgid="6479401489471690359">"輸入 SIM 卡 PIN 碼"</string> <string name="kg_pin_instructions" msgid="7355933174673539021">"輸入 PIN 碼"</string> <string name="kg_password_instructions" msgid="7179782578809398050">"輸入密碼"</string> @@ -1644,7 +1644,7 @@ <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK 碼應由 8 位數字組成。"</string> <string name="kg_invalid_puk" msgid="4809502818518963344">"請重新輸入正確的 PUK 碼。如果嘗試輸入的次數過多,SIM 卡將永久停用。"</string> <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN 碼不符"</string> - <string name="kg_login_too_many_attempts" msgid="699292728290654121">"圖形嘗試次數過多"</string> + <string name="kg_login_too_many_attempts" msgid="699292728290654121">"圖案嘗試次數過多"</string> <string name="kg_login_instructions" msgid="3619844310339066827">"如要解鎖,請以 Google 帳戶登入。"</string> <string name="kg_login_username_hint" msgid="1765453775467133251">"使用者名稱 (電子郵件)"</string> <string name="kg_login_password_hint" msgid="3330530727273164402">"密碼"</string> @@ -1654,16 +1654,16 @@ <string name="kg_login_checking_password" msgid="4676010303243317253">"正在檢查帳戶…"</string> <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"您已輸入錯誤的 PIN 碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"您已輸入錯誤的密碼 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> - <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> + <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。\n\n請在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒後再試一次。"</string> <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"您嘗試了 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次仍未能成功解開這部上鎖的平板電腦。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,平板電腦將回復原廠設定,所有使用者資料均會失去。"</string> <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"您已 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次無法解鎖 Android TV 裝置。如果再失敗 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次,Android TV 裝置將回復原廠設定,所有使用者資料均會遺失。"</string> <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"您嘗試了 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次仍未能成功解開這部上鎖的手機。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,手機將回復原廠設定,所有使用者資料均會失去。"</string> <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"您嘗試了 <xliff:g id="NUMBER">%d</xliff:g> 次仍未能成功解開這部上鎖的平板電腦。平板電腦現在將回復原廠設定。"</string> <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"您已 <xliff:g id="NUMBER">%d</xliff:g> 次無法解鎖 Android TV 裝置,Android TV 裝置現在將回復原廠設定。"</string> <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"您嘗試了 <xliff:g id="NUMBER">%d</xliff:g> 次仍未能成功解開這部上鎖的手機。手機現在將回復原廠設定。"</string> - <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> - <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用電郵帳戶解鎖 Android TV 裝置。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> - <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"您已畫錯解鎖圖形 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> + <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的平板電腦。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> + <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次。如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您使用電郵帳戶解鎖 Android TV 裝置。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> + <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"移除"</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量 (比建議的音量更大聲) 嗎?\n\n長時間聆聽高分貝音量可能會導致您的聽力受損。"</string> @@ -1843,7 +1843,7 @@ <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"第二個工作<xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"第三個工作<xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"取消固定時必須輸入 PIN"</string> - <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須提供解鎖圖形"</string> + <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須提供解鎖圖案"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消固定時必須輸入密碼"</string> <string name="package_installed_device_owner" msgid="7035926868974878525">"已由您的管理員安裝"</string> <string name="package_updated_device_owner" msgid="7560272363805506941">"已由您的管理員更新"</string> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 5df3dde82b3e..b515abc4000f 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -448,5 +448,6 @@ <color name="accessibility_color_inversion_background">#546E7A</color> <!-- Color of camera light when camera is in use --> - <color name="camera_privacy_light">#FFFFFF</color> + <color name="camera_privacy_light_day">#FFFFFF</color> + <color name="camera_privacy_light_night">#FFFFFF</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 689ff66a3b4d..df7f34a357c3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2998,12 +2998,6 @@ </string-array> - <!-- When migrating notification settings into the permission framework, whether all existing - apps should be marked as 'user-set' (true) or whether only the apps that have explicitly - modified notification settings should be marked as 'user-set' (false). Users will not see - system generated permission prompts for 'user-set' apps. --> - <bool name="config_notificationForceUserSetOnUpgrade">true</bool> - <!-- Default Gravity setting for the system Toast view. Equivalent to: Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM --> <integer name="config_toastDefaultGravity">0x00000051</integer> @@ -5662,6 +5656,9 @@ <!-- Whether or not to enable the lock screen entry point for the QR code scanner. --> <bool name="config_enableQrCodeScannerOnLockScreen">false</bool> + <!-- Default component for QR code scanner --> + <string name="config_defaultQrCodeComponent"></string> + <!-- Whether Low Power Standby is supported and can be enabled. --> <bool name="config_lowPowerStandbySupported">false</bool> @@ -5787,4 +5784,17 @@ <!-- List of the labels of requestable device state config values --> <string-array name="config_deviceStatesAvailableForAppRequests"/> + + <!-- Interval in milliseconds to average light sensor values for camera light brightness --> + <integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer> + <!-- Light sensor's lux value to use as the threshold between using day or night brightness --> + <integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer> + + <!-- List of system components which are allowed to receive ServiceState entries in an + un-sanitized form, even if the location toggle is off. This is intended ONLY for system + components, such as the telephony stack, which require access to the full ServiceState for + tasks such as network registration. --> + <string-array name="config_serviceStateLocationAllowedPackages"> + <item>"com.android.phone"</item> + </string-array> </resources> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index d9ac5164f705..682ce46d132d 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -35,7 +35,7 @@ rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max If this is configured as an empty string, the system default will be applied. --> - <string name="config_tcp_buffers" translatable="false"></string> + <string name="config_tcp_buffers" translatable="false">2097152,6291456,16777216,512000,2097152,8388608</string> <java-symbol type="string" name="config_tcp_buffers" /> <!-- What source to use to estimate link upstream and downstream bandwidth capacities. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 443f9a628a7e..4e1839d6e338 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4708,6 +4708,7 @@ <java-symbol type="string" name="config_wearSysUiPackage"/> <java-symbol type="string" name="config_wearSysUiMainActivity"/> + <java-symbol type="string" name="config_defaultQrCodeComponent"/> <java-symbol type="dimen" name="secondary_rounded_corner_radius" /> <java-symbol type="dimen" name="secondary_rounded_corner_radius_top" /> @@ -4753,7 +4754,10 @@ <!-- For VirtualDeviceManager --> <java-symbol type="string" name="vdm_camera_access_denied" /> - <java-symbol type="color" name="camera_privacy_light"/> + <java-symbol type="color" name="camera_privacy_light_day"/> + <java-symbol type="color" name="camera_privacy_light_night"/> + <java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/> + <java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/> <java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" /> <java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" /> @@ -4772,8 +4776,8 @@ <java-symbol type="integer" name="config_bg_current_drain_exempted_types" /> <java-symbol type="bool" name="config_bg_current_drain_high_threshold_by_bg_location" /> <java-symbol type="drawable" name="ic_swap_horiz" /> - <java-symbol type="bool" name="config_notificationForceUserSetOnUpgrade" /> <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" /> + <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" /> <!-- For app language picker --> <java-symbol type="string" name="system_locale_title" /> diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index c9a18dafe11d..c9e02f8a998d 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -38,8 +38,8 @@ import static org.junit.Assert.assertTrue; import android.os.Parcel; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.PasswordValidationError; @@ -324,9 +324,59 @@ public class PasswordMetricsTest { PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); } + @Test + public void testValidatePasswordMetrics_pinAndComplexityHigh() { + PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); + PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); + actualMetrics.length = 6; + actualMetrics.seqLength = 1; + + assertValidationErrors( + validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), + PasswordValidationError.TOO_SHORT, 8); + } + + @Test + public void testValidatePasswordMetrics_nonAllNumberPasswordAndComplexityHigh() { + PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + actualMetrics.length = 5; + actualMetrics.nonNumeric = 1; + actualMetrics.seqLength = 1; + + assertValidationErrors( + validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), + PasswordValidationError.TOO_SHORT, 6); + } + + @Test + public void testValidatePasswordMetrics_allNumberPasswordAndComplexityHigh() { + PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + actualMetrics.length = 6; + actualMetrics.seqLength = 1; + + assertValidationErrors( + validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), + PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC, 8); + } + + @Test + public void testValidatePasswordMetrics_allNumberPasswordAndRequireNonNumeric() { + PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + adminMetrics.nonNumeric = 1; + PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + actualMetrics.length = 6; + actualMetrics.seqLength = 1; + + assertValidationErrors( + validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), + PasswordValidationError.NOT_ENOUGH_NON_DIGITS, 1); + } + /** * @param expected sequense of validation error codes followed by requirement values, must have - * even number of elements. Empty means no errors. + * even number of elements. Empty means no errors. */ private void assertValidationErrors( List<PasswordValidationError> actualErrors, int... expected) { diff --git a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java index 969357f66dde..22feecbd899f 100644 --- a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java +++ b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; -import org.junit.Ignore; import org.junit.Test; import java.util.Set; @@ -32,7 +31,6 @@ import java.util.Set; @Presubmit public class AppSearchShortcutInfoTest { - @Ignore("b/208375334") @Test public void testBuildShortcutAndGetValue() { final String category = @@ -51,7 +49,7 @@ public class AppSearchShortcutInfoTest { final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(/*packageName=*/"", id) .setActivity(activity) - .setLongLabel(id) + .setShortLabel(id) .setIconResName(shortcutIconResName) .setIntent(shortcutIntent) .setPerson(person) @@ -64,11 +62,13 @@ public class AppSearchShortcutInfoTest { assertThat(shortcut.getId()).isEqualTo(id); assertThat(shortcut.getShortLabel()).isEqualTo(id); assertThat(shortcut.getIconResName()).isEqualTo(shortcutIconResName); - assertThat(shortcut.getIntent().toString()).isEqualTo(shortcut.toString()); + assertThat(shortcut.getIntent().toString()).isEqualTo(shortcutIntent.toString()); assertThat(shortcut.getPersons().length).isEqualTo(1); - assertThat(shortcut.getPersons()[0]).isEqualTo(person); + final Person target = shortcut.getPersons()[0]; + assertThat(target.getName()).isEqualTo(person.getName()); + assertThat(target.isBot()).isEqualTo(person.isBot()); + assertThat(target.isImportant()).isEqualTo(person.isImportant()); assertThat(shortcut.getCategories()).isEqualTo(categorySet); - assertThat(shortcut.getFlags()).isEqualTo(ShortcutInfo.FLAG_LONG_LIVED); assertThat(shortcut.getActivity()).isEqualTo(activity); } } diff --git a/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java b/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java index 786c22bf04f6..9b6bcda8aed0 100644 --- a/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java +++ b/core/tests/coretests/src/android/view/RenderNodeAnimatorTest.java @@ -19,17 +19,24 @@ package android.view; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; +import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.MediumTest; import androidx.test.rule.ActivityTestRule; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @MediumTest public class RenderNodeAnimatorTest { @Rule @@ -57,4 +64,46 @@ public class RenderNodeAnimatorTest { anim.start(); // should initialize mTransformationInfo assertNotNull(view.mTransformationInfo); } + + @Test + public void testViewDetachCancelsRenderNodeAnimator() { + // Start a RenderNodeAnimator with a long duration time, then detach the target view + // before the animation completes. Detaching of a View from a window should force cancel all + // RenderNodeAnimators + CountDownLatch latch = new CountDownLatch(1); + + FrameLayout container = new FrameLayout(getContext()); + View view = new View(getContext()); + + getActivity().runOnUiThread(() -> { + container.addView(view, new FrameLayout.LayoutParams(100, 100)); + getActivity().setContentView(container); + }); + getActivity().runOnUiThread(() -> { + RenderNodeAnimator anim = new RenderNodeAnimator(0, 0, 10f, 30f); + anim.setDuration(10000); + anim.setTarget(view); + anim.addListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + latch.countDown(); + } + }); + + anim.start(); + }); + + getActivity().runOnUiThread(()-> { + container.removeView(view); + }); + + try { + Assert.assertTrue("onAnimationEnd not invoked", + latch.await(3000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException excep) { + Assert.fail("Interrupted waiting for onAnimationEnd callback"); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java index 0c009a055e2b..4cf9c3fe75b9 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java @@ -39,6 +39,10 @@ public class ResolverWrapperActivity extends ResolverActivity { static final OverrideData sOverrides = new OverrideData(); private UsageStatsManager mUsm; + public ResolverWrapperActivity() { + super(/* isIntentPicker= */ true); + } + @Override public ResolverListAdapter createResolverListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java index 2262c057d842..385879210d4a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java @@ -35,6 +35,7 @@ import java.io.File; @RunWith(AndroidJUnit4.class) @SmallTest +@SuppressWarnings("GuardedBy") public class BatteryStatsHistoryIteratorTest { private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; @@ -124,7 +125,10 @@ public class BatteryStatsHistoryIteratorTest { // More than 32k strings final int eventCount = 0x7FFF + 100; for (int i = 0; i < eventCount; i++) { - mBatteryStats.noteAlarmStartLocked("a" + i, null, APP_UID, 3_000_000, 2_000_000); + // Names repeat in order to verify de-duping of identical history tags. + String name = "a" + (i % 10); + mBatteryStats.noteAlarmStartLocked(name, null, APP_UID, 3_000_000, 2_000_000); + mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID, 3_500_000, 2_500_000); } final BatteryStatsHistoryIterator iterator = @@ -149,10 +153,23 @@ public class BatteryStatsHistoryIteratorTest { assertThat(item.time).isEqualTo(2_000_000); for (int i = 0; i < eventCount; i++) { + String name = "a" + (i % 10); assertThat(iterator.next(item)).isTrue(); + // Skip a blank event inserted at the start of every buffer + if (item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) { + assertThat(iterator.next(item)).isTrue(); + } assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START); - assertThat(item.eventTag.string).isEqualTo("a" + i); + assertThat(item.eventTag.string).isEqualTo(name); + + assertThat(iterator.next(item)).isTrue(); + if (item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) { + assertThat(iterator.next(item)).isTrue(); + } + assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM + | BatteryStats.HistoryItem.EVENT_FLAG_FINISH); + assertThat(item.eventTag.string).isEqualTo(name); } assertThat(iterator.next(item)).isFalse(); diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index ebf5832147f7..f030d80a3533 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -80,5 +80,6 @@ <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.READ_DEVICE_CONFIG" /> <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> + <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" /> </privapp-permissions> </permissions> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 60da2e8cba27..12d3d642a862 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2485,6 +2485,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "323235828": { + "message": "Delaying app transition for recents animation to finish", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "327461496": { "message": "Complete pause: %s", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 5fd53adc409a..dadbd8d2d1aa 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -1611,6 +1611,11 @@ public final class RenderNode { nEndAllAnimators(mNativeRenderNode); } + /** @hide */ + public void forceEndAnimators() { + nForceEndAnimators(mNativeRenderNode); + } + /////////////////////////////////////////////////////////////////////////// // Regular JNI methods /////////////////////////////////////////////////////////////////////////// @@ -1633,6 +1638,8 @@ public final class RenderNode { private static native void nEndAllAnimators(long renderNode); + private static native void nForceEndAnimators(long renderNode); + /////////////////////////////////////////////////////////////////////////// // @CriticalNative methods /////////////////////////////////////////////////////////////////////////// diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java index 9ad6f3adbd33..6fff52a20062 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java @@ -206,6 +206,8 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { putSignatureImpl("NONEwithECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE"); + putSignatureImpl("Ed25519", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$Ed25519"); putSignatureImpl("SHA1withECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1"); put("Alg.Alias.Signature.ECDSA", "SHA1withECDSA"); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java index 8289671de212..5216a908826b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java @@ -29,7 +29,10 @@ import libcore.util.EmptyArray; import java.io.ByteArrayOutputStream; import java.security.InvalidKeyException; import java.security.SignatureSpi; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures. @@ -37,6 +40,10 @@ import java.util.List; * @hide */ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase { + private static final Set<String> ACCEPTED_SIGNING_SCHEMES = Set.of( + KeyProperties.KEY_ALGORITHM_EC.toLowerCase(), + NamedParameterSpec.ED25519.getName().toLowerCase(), + "eddsa"); public final static class NONE extends AndroidKeyStoreECDSASignatureSpi { public NONE() { @@ -114,6 +121,18 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature } } + public static final class Ed25519 extends AndroidKeyStoreECDSASignatureSpi { + public Ed25519() { + // Ed25519 uses an internal digest system. + super(KeymasterDefs.KM_DIGEST_NONE); + } + + @Override + protected String getAlgorithm() { + return NamedParameterSpec.ED25519.getName(); + } + } + public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi { public SHA1() { super(KeymasterDefs.KM_DIGEST_SHA1); @@ -174,9 +193,10 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature @Override protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { - if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) { + if (!ACCEPTED_SIGNING_SCHEMES.contains(key.getAlgorithm().toLowerCase())) { throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() - + ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported"); + + ". Only" + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray()) + + " supported"); } long keySizeBits = -1; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index faada1aa03ef..1cd422087ccb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -234,13 +234,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { // If the activity belongs to the current app process, we treat it as a new activity launch. - final Activity activity = ActivityThread.currentActivityThread().getActivity(activityToken); + final Activity activity = getActivity(activityToken); if (activity != null) { onActivityCreated(activity); - updateCallbackIfNecessary(); return; } - // TODO: handle for activity in other process. + + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer == null || taskContainer.isInPictureInPicture()) { + // We don't embed activity when it is in PIP. + return; + } + + // If the activity belongs to a different app process, we treat it as starting new intent, + // since both actions might result in a new activity that should appear in an organized + // TaskFragment. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId, + activityIntent, null /* launchingActivity */); + if (targetContainer == null) { + // When there is no split rule matched, try to place it in the top container like a + // normal launch. + targetContainer = taskContainer.getTopTaskFragmentContainer(); + } + if (targetContainer == null) { + return; + } + wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken); + mPresenter.applyTransaction(wct); + // Because the activity does not belong to the organizer process, we wait until + // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */ @@ -327,16 +350,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ // TODO(b/190433398): Break down into smaller functions. void handleActivityCreated(@NonNull Activity launchedActivity) { - if (isInPictureInPicture(launchedActivity)) { - // We don't embed activity when it is in PIP. + if (isInPictureInPicture(launchedActivity) || launchedActivity.isFinishing()) { + // We don't embed activity when it is in PIP, or finishing. return; } - final List<EmbeddingRule> splitRules = getSplitRules(); - final TaskFragmentContainer currentContainer = getContainerWithActivity( - launchedActivity.getActivityToken()); + final TaskFragmentContainer currentContainer = getContainerWithActivity(launchedActivity); // Check if the activity is configured to always be expanded. - if (shouldExpand(launchedActivity, null, splitRules)) { + if (shouldExpand(launchedActivity, null /* intent */)) { if (shouldContainerBeExpanded(currentContainer)) { // Make sure that the existing container is expanded mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken()); @@ -375,7 +396,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow( launchedActivity.getActivityToken()); if (belowToken != null) { - activityBelow = ActivityThread.currentActivityThread().getActivity(belowToken); + activityBelow = getActivity(belowToken); } } if (activityBelow == null) { @@ -384,7 +405,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check if the split is already set. final TaskFragmentContainer activityBelowContainer = getContainerWithActivity( - activityBelow.getActivityToken()); + activityBelow); if (currentContainer != null && activityBelowContainer != null) { final SplitContainer existingSplit = getActiveSplitForContainers(currentContainer, activityBelowContainer); @@ -394,8 +415,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity, - splitRules); + final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity); if (splitPairRule == null) { return; } @@ -409,8 +429,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // We don't embed activity when it is in PIP. return; } - final TaskFragmentContainer currentContainer = getContainerWithActivity( - activity.getActivityToken()); + final TaskFragmentContainer currentContainer = getContainerWithActivity(activity); if (currentContainer != null) { // Changes to activities in controllers are handled in @@ -422,6 +441,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen launchPlaceholderIfNecessary(activity); } + @VisibleForTesting + void onActivityDestroyed(@NonNull Activity activity) { + // Remove any pending appeared activity, as the server won't send finished activity to the + // organizer. + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + mTaskContainers.valueAt(i).cleanupPendingAppearedActivity(activity); + } + // We didn't trigger the callback if there were any pending appeared activities, so check + // again after the pending is removed. + updateCallbackIfNecessary(); + } + /** * Called when we have been waiting too long for the TaskFragment to become non-empty after * creation. @@ -431,11 +462,143 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** + * When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer} + * that we should reparent the new activity to if there is any embedding rule matched. + * + * @param wct {@link WindowContainerTransaction} including all the window change + * requests. The caller is responsible to call + * {@link android.window.TaskFragmentOrganizer#applyTransaction}. + * @param taskId The Task to start the activity in. + * @param intent The {@link Intent} for starting the new launched activity. + * @param launchingActivity The {@link Activity} that starts the new activity. We will + * prioritize to split the new activity with it if it is not + * {@code null}. + * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there + * is no embedding rule matched. + */ + @VisibleForTesting + @Nullable + TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { + /* + * We will check the following to see if there is any embedding rule matched: + * 1. Whether the new activity intent should always expand. + * 2. Whether the launching activity (if set) should be split with the new activity intent. + * 3. Whether the top activity (if any) should be split with the new activity intent. + * 4. Whether the top activity (if any) in other split should be split with the new + * activity intent. + */ + + // 1. Whether the new activity intent should always expand. + if (shouldExpand(null /* activity */, intent)) { + return createEmptyExpandedContainer(wct, taskId, launchingActivity); + } + + // 2. Whether the launching activity (if set) should be split with the new activity intent. + if (launchingActivity != null) { + final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, + launchingActivity, intent, true /* respectClearTop */); + if (container != null) { + return container; + } + } + + // 3. Whether the top activity (if any) should be split with the new activity intent. + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) { + // There is no other activity in the Task to check split with. + return null; + } + final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer(); + final Activity topActivity = topContainer.getTopNonFinishingActivity(); + if (topActivity != null && topActivity != launchingActivity) { + final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct, + topActivity, intent, false /* respectClearTop */); + if (container != null) { + return container; + } + } + + // 4. Whether the top activity (if any) in other split should be split with the new + // activity intent. + final SplitContainer topSplit = getActiveSplitForContainer(topContainer); + if (topSplit == null) { + return null; + } + final TaskFragmentContainer otherTopContainer = + topSplit.getPrimaryContainer() == topContainer + ? topSplit.getSecondaryContainer() + : topSplit.getPrimaryContainer(); + final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity(); + if (otherTopActivity != null && otherTopActivity != launchingActivity) { + return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent, + false /* respectClearTop */); + } + return null; + } + + /** + * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into. + */ + @Nullable + private TaskFragmentContainer createEmptyExpandedContainer( + @NonNull WindowContainerTransaction wct, int taskId, + @Nullable Activity launchingActivity) { + // We need an activity in the organizer process in the same Task to use as the owner + // activity, as well as to get the Task window info. + final Activity activityInTask; + if (launchingActivity != null) { + activityInTask = launchingActivity; + } else { + final TaskContainer taskContainer = getTaskContainer(taskId); + activityInTask = taskContainer != null + ? taskContainer.getTopNonFinishingActivity() + : null; + } + if (activityInTask == null) { + // Can't find any activity in the Task that we can use as the owner activity. + return null; + } + final TaskFragmentContainer expandedContainer = newContainer(null /* activity */, + activityInTask, taskId); + mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(), + activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); + return expandedContainer; + } + + /** + * Returns a container for the new activity intent to launch into as splitting with the primary + * activity. + */ + @Nullable + private TaskFragmentContainer getSecondaryContainerForSplitIfAny( + @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, + @NonNull Intent intent, boolean respectClearTop) { + final SplitPairRule splitRule = getSplitRule(primaryActivity, intent); + if (splitRule == null) { + return null; + } + final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); + final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); + if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() + && (canReuseContainer(splitRule, splitContainer.getSplitRule()) + // TODO(b/231845476) we should always respect clearTop. + || !respectClearTop)) { + // Can launch in the existing secondary container if the rules share the same + // presentation. + return splitContainer.getSecondaryContainer(); + } + // Create a new TaskFragment to split with the primary activity for the new activity. + return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, splitRule); + } + + /** * Returns a container that this activity is registered with. An activity can only belong to one * container, or no container at all. */ @Nullable - TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { + TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) { + final IBinder activityToken = activity.getActivityToken(); for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; for (TaskFragmentContainer container : containers) { @@ -465,12 +628,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } - final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId, this); if (!mTaskContainers.contains(taskId)) { mTaskContainers.put(taskId, new TaskContainer(taskId)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); - taskContainer.mContainers.add(container); + final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskContainer, + this); if (!taskContainer.isTaskBoundsInitialized()) { // Get the initial bounds before the TaskFragment has appeared. final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); @@ -500,14 +663,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); } - mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer); + primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer); } /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { - final int taskId = container.getTaskId(); - final TaskContainer taskContainer = mTaskContainers.get(taskId); + final TaskContainer taskContainer = container.getTaskContainer(); if (taskContainer == null) { return; } @@ -545,8 +707,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ void removeContainer(@NonNull TaskFragmentContainer container) { // Remove all split containers that included this one - final int taskId = container.getTaskId(); - final TaskContainer taskContainer = mTaskContainers.get(taskId); + final TaskContainer taskContainer = container.getTaskContainer(); if (taskContainer == null) { return; } @@ -637,8 +798,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitContainer == null) { return; } - final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId()) - .mSplitContainers; + final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers; if (splitContainer != splitContainers.get(splitContainers.size() - 1)) { // Skip position update - it isn't the topmost split. return; @@ -659,9 +819,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns the top active split container that has the provided container, if available. */ @Nullable - private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) { - final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId()) - .mSplitContainers; + private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) { + if (container == null) { + return null; + } + final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers; if (splitContainers.isEmpty()) { return null; } @@ -679,15 +841,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns the active split that has the provided containers as primary and secondary or as * secondary and primary, if available. */ + @VisibleForTesting @Nullable - private SplitContainer getActiveSplitForContainers( + SplitContainer getActiveSplitForContainers( @NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer) { - final List<SplitContainer> splitContainers = mTaskContainers.get(firstContainer.getTaskId()) + final List<SplitContainer> splitContainers = firstContainer.getTaskContainer() .mSplitContainers; - if (splitContainers == null) { - return null; - } for (int i = splitContainers.size() - 1; i >= 0; i--) { final SplitContainer splitContainer = splitContainers.get(i); final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); @@ -713,15 +873,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } boolean launchPlaceholderIfNecessary(@NonNull Activity activity) { - final TaskFragmentContainer container = getContainerWithActivity( - activity.getActivityToken()); + final TaskFragmentContainer container = getContainerWithActivity(activity); // Don't launch placeholder if the container is occluded. if (container != null && container != getTopActiveContainer(container.getTaskId())) { return false; } - SplitContainer splitContainer = container != null ? getActiveSplitForContainer(container) - : null; + final SplitContainer splitContainer = getActiveSplitForContainer(container); if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { // Don't launch placeholder in primary split container return false; @@ -856,11 +1014,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == null) { return false; } - final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId()) - .mSplitContainers; - if (splitContainers == null) { - return true; - } + final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers; for (SplitContainer splitContainer : splitContainers) { if (container.equals(splitContainer.getPrimaryContainer()) || container.equals(splitContainer.getSecondaryContainer())) { @@ -875,9 +1029,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * if available. */ @Nullable - private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity, - @NonNull Intent secondaryActivityIntent, @NonNull List<EmbeddingRule> splitRules) { - for (EmbeddingRule rule : splitRules) { + private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryActivityIntent) { + for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitPairRule)) { continue; } @@ -893,9 +1047,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns a split rule for the provided pair of primary and secondary activities if available. */ @Nullable - private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity, @NonNull List<EmbeddingRule> splitRules) { - for (EmbeddingRule rule : splitRules) { + private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitPairRule)) { continue; } @@ -932,16 +1086,24 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return mHandler; } + int getTaskId(@NonNull Activity activity) { + // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an + // IPC call. + final TaskFragmentContainer container = getContainerWithActivity(activity); + return container != null ? container.getTaskId() : activity.getTaskId(); + } + + @Nullable + Activity getActivity(@NonNull IBinder activityToken) { + return ActivityThread.currentActivityThread().getActivity(activityToken); + } + /** * Returns {@code true} if an Activity with the provided component name should always be * expanded to occupy full task bounds. Such activity must not be put in a split. */ - private static boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent, - List<EmbeddingRule> splitRules) { - if (splitRules == null) { - return false; - } - for (EmbeddingRule rule : splitRules) { + private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { + for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof ActivityRule)) { continue; } @@ -995,8 +1157,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer, @NonNull Activity associatedActivity) { - TaskFragmentContainer associatedContainer = getContainerWithActivity( - associatedActivity.getActivityToken()); + final TaskFragmentContainer associatedContainer = getContainerWithActivity( + associatedActivity); if (associatedContainer == null) { return false; } @@ -1046,6 +1208,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public void onActivityConfigurationChanged(Activity activity) { SplitController.this.onActivityConfigurationChanged(activity); } + + @Override + public void onActivityPostDestroyed(Activity activity) { + SplitController.this.onActivityDestroyed(activity); + } } /** Executor that posts on the main application thread. */ @@ -1079,130 +1246,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return super.onStartActivity(who, intent, options); } - if (shouldExpand(null, intent, getSplitRules())) { - setLaunchingInExpandedContainer(launchingActivity, options); - } else if (!splitWithLaunchingActivity(launchingActivity, intent, options)) { - setLaunchingInSameSideContainer(launchingActivity, intent, options); + final int taskId = getTaskId(launchingActivity); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct, + taskId, intent, launchingActivity); + if (launchedInTaskFragment != null) { + mPresenter.applyTransaction(wct); + // Amend the request to let the WM know that the activity should be placed in the + // dedicated container. + options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, + launchedInTaskFragment.getTaskFragmentToken()); } return super.onStartActivity(who, intent, options); } - - private void setLaunchingInExpandedContainer(Activity launchingActivity, Bundle options) { - TaskFragmentContainer newContainer = mPresenter.createNewExpandedContainer( - launchingActivity); - - // Amend the request to let the WM know that the activity should be placed in the - // dedicated container. - options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, - newContainer.getTaskFragmentToken()); - } - - /** - * Returns {@code true} if the activity that is going to be started via the - * {@code intent} should be paired with the {@code launchingActivity} and is set to be - * launched in the side container. - */ - private boolean splitWithLaunchingActivity(Activity launchingActivity, Intent intent, - Bundle options) { - final SplitPairRule splitPairRule = getSplitRule(launchingActivity, intent, - getSplitRules()); - if (splitPairRule == null) { - return false; - } - - // Check if there is any existing side container to launch into. - TaskFragmentContainer secondaryContainer = findSideContainerForNewLaunch( - launchingActivity, splitPairRule); - if (secondaryContainer == null) { - // Create a new split with an empty side container. - secondaryContainer = mPresenter - .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule); - } - - // Amend the request to let the WM know that the activity should be placed in the - // dedicated container. - options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, - secondaryContainer.getTaskFragmentToken()); - return true; - } - - /** - * Finds if there is an existing split side {@link TaskFragmentContainer} that can be used - * for the new rule. - */ - @Nullable - private TaskFragmentContainer findSideContainerForNewLaunch(Activity launchingActivity, - SplitPairRule splitPairRule) { - final TaskFragmentContainer launchingContainer = getContainerWithActivity( - launchingActivity.getActivityToken()); - if (launchingContainer == null) { - return null; - } - - // We only check if the launching activity is the primary of the split. We will check - // if the launching activity is the secondary in #setLaunchingInSameSideContainer. - final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer); - if (splitContainer == null - || splitContainer.getPrimaryContainer() != launchingContainer) { - return null; - } - - if (canReuseContainer(splitPairRule, splitContainer.getSplitRule())) { - return splitContainer.getSecondaryContainer(); - } - return null; - } - - /** - * Checks if the activity that is going to be started via the {@code intent} should be - * paired with the existing top activity which is currently paired with the - * {@code launchingActivity}. If so, set the activity to be launched in the same side - * container of the {@code launchingActivity}. - */ - private void setLaunchingInSameSideContainer(Activity launchingActivity, Intent intent, - Bundle options) { - final TaskFragmentContainer launchingContainer = getContainerWithActivity( - launchingActivity.getActivityToken()); - if (launchingContainer == null) { - return; - } - - final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer); - if (splitContainer == null) { - return; - } - - if (splitContainer.getSecondaryContainer() != launchingContainer) { - return; - } - - // The launching activity is on the secondary container. Retrieve the primary - // activity from the other container. - Activity primaryActivity = - splitContainer.getPrimaryContainer().getTopNonFinishingActivity(); - if (primaryActivity == null) { - return; - } - - final SplitPairRule splitPairRule = getSplitRule(primaryActivity, intent, - getSplitRules()); - if (splitPairRule == null) { - return; - } - - // Can only launch in the same container if the rules share the same presentation. - if (!canReuseContainer(splitPairRule, splitContainer.getSplitRule())) { - return; - } - - // Amend the request to let the WM know that the activity should be placed in the - // dedicated container. This is necessary for the case that the activity is started - // into a new Task, or new Task will be escaped from the current host Task and be - // displayed in fullscreen. - options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, - launchingContainer.getTaskFragmentToken()); - } } /** @@ -1222,8 +1279,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { return false; } - return rule1.getSplitRatio() == rule2.getSplitRatio() - && rule1.getLayoutDirection() == rule2.getLayoutDirection(); + final SplitPairRule pairRule1 = (SplitPairRule) rule1; + final SplitPairRule pairRule2 = (SplitPairRule) rule2; + // TODO(b/231655482): add util method to do the comparison in SplitPairRule. + return pairRule1.getSplitRatio() == pairRule2.getSplitRatio() + && pairRule1.getLayoutDirection() == pairRule2.getLayoutDirection() + && pairRule1.getFinishPrimaryWithSecondary() + == pairRule2.getFinishPrimaryWithSecondary() + && pairRule1.getFinishSecondaryWithPrimary() + == pairRule2.getFinishSecondaryWithPrimary(); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 06c1d4ec8d32..43d0402c1525 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -16,8 +16,6 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - import android.app.Activity; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; @@ -100,10 +98,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Creates a new split with the primary activity and an empty secondary container. * @return The newly created secondary container. */ - TaskFragmentContainer createNewSplitWithEmptySideContainer(@NonNull Activity primaryActivity, + @NonNull + TaskFragmentContainer createNewSplitWithEmptySideContainer( + @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull SplitPairRule rule) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final Rect parentBounds = getParentContainerBounds(primaryActivity); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, isLtr(primaryActivity, rule)); @@ -127,8 +125,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); - applyTransaction(wct); - return secondaryContainer; } @@ -155,8 +151,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); + final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( + secondaryActivity); + TaskFragmentContainer containerToAvoid = primaryContainer; + if (rule.shouldClearTop() && curSecondaryContainer != null) { + // Do not reuse the current TaskFragment if the rule is to clear top. + containerToAvoid = curSecondaryContainer; + } final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, - secondaryActivity, secondaryRectBounds, primaryContainer); + secondaryActivity, secondaryRectBounds, containerToAvoid); // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); @@ -167,21 +170,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } /** - * Creates a new expanded container. - */ - TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) { - final TaskFragmentContainer newContainer = mController.newContainer(null /* activity */, - launchingActivity, launchingActivity.getTaskId()); - - final WindowContainerTransaction wct = new WindowContainerTransaction(); - createTaskFragment(wct, newContainer.getTaskFragmentToken(), - launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); - - applyTransaction(wct); - return newContainer; - } - - /** * Creates a new container or resizes an existing container for activity to the provided bounds. * @param activity The activity to be re-parented to the container if necessary. * @param containerToAvoid Re-parent from this container if an activity is already in it. @@ -189,8 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { private TaskFragmentContainer prepareContainerForActivity( @NonNull WindowContainerTransaction wct, @NonNull Activity activity, @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) { - TaskFragmentContainer container = mController.getContainerWithActivity( - activity.getActivityToken()); + TaskFragmentContainer container = mController.getContainerWithActivity(activity); final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); if (container == null || container == containerToAvoid) { container = mController.newContainer(activity, taskId); @@ -230,7 +217,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { isLtr(launchingActivity, rule)); TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( - launchingActivity.getActivityToken()); + launchingActivity); if (primaryContainer == null) { primaryContainer = mController.newContainer(launchingActivity, launchingActivity.getTaskId()); @@ -291,8 +278,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } - final TaskContainer taskContainer = mController.getTaskContainer( - updatedContainer.getTaskId()); + final TaskContainer taskContainer = updatedContainer.getTaskContainer(); final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( primaryRectBounds); updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); @@ -456,18 +442,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { - final int taskId = container.getTaskId(); - final TaskContainer taskContainer = mController.getTaskContainer(taskId); - if (taskContainer == null) { - throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId); - } - return taskContainer.getTaskBounds(); + return container.getTaskContainer().getTaskBounds(); } @NonNull Rect getParentContainerBounds(@NonNull Activity activity) { - final TaskFragmentContainer container = mController.getContainerWithActivity( - activity.getActivityToken()); + final TaskFragmentContainer container = mController.getContainerWithActivity(activity); if (container != null) { return getParentContainerBounds(container); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 57628435419e..0ea5603b1f3d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -16,12 +16,14 @@ package androidx.window.extensions.embedding; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; @@ -62,6 +64,9 @@ class TaskContainer { final Set<IBinder> mFinishedContainer = new ArraySet<>(); TaskContainer(int taskId) { + if (taskId == INVALID_TASK_ID) { + throw new IllegalArgumentException("Invalid Task id"); + } mTaskId = taskId; } @@ -130,4 +135,30 @@ class TaskContainer { boolean isEmpty() { return mContainers.isEmpty() && mFinishedContainer.isEmpty(); } + + /** Removes the pending appeared activity from all TaskFragments in this Task. */ + void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { + for (TaskFragmentContainer container : mContainers) { + container.removePendingAppearedActivity(pendingAppearedActivity); + } + } + + @Nullable + TaskFragmentContainer getTopTaskFragmentContainer() { + if (mContainers.isEmpty()) { + return null; + } + return mContainers.get(mContainers.size() - 1); + } + + @Nullable + Activity getTopNonFinishingActivity() { + for (int i = mContainers.size() - 1; i >= 0; i--) { + final Activity activity = mContainers.get(i).getTopNonFinishingActivity(); + if (activity != null) { + return activity; + } + } + return null; + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index b3becad3dc5a..cdee9e386b33 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -17,6 +17,8 @@ package androidx.window.extensions.embedding; import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MTRANS_X; +import static android.graphics.Matrix.MTRANS_Y; import android.graphics.Rect; import android.view.Choreographer; @@ -96,22 +98,20 @@ class TaskFragmentAnimationAdapter { mTarget.localBounds.left, mTarget.localBounds.top); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); - - // Open/close animation may scale up the surface. Apply an inverse scale to the window crop - // so that it will not be covering other windows. - mVecs[1] = mVecs[2] = 0; - mVecs[0] = mVecs[3] = 1; - mTransformation.getMatrix().mapVectors(mVecs); - mVecs[0] = 1.f / mVecs[0]; - mVecs[3] = 1.f / mVecs[3]; - final Rect clipRect = mTarget.localBounds; - mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f); - mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f); - mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f); - mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f); - mRect.offsetTo(Math.round(mTarget.localBounds.width() * (1 - mVecs[0]) / 2.f), - Math.round(mTarget.localBounds.height() * (1 - mVecs[3]) / 2.f)); - t.setWindowCrop(mLeash, mRect); + // Get current animation position. + final int positionX = Math.round(mMatrix[MTRANS_X]); + final int positionY = Math.round(mMatrix[MTRANS_Y]); + // The exiting surface starts at position: mTarget.localBounds and moves with + // positionX varying. Offset our crop region by the amount we have slided so crop + // regions stays exactly on the original container in split. + final int cropOffsetX = mTarget.localBounds.left - positionX; + final int cropOffsetY = mTarget.localBounds.top - positionY; + final Rect cropRect = new Rect(); + cropRect.set(mTarget.localBounds); + // Because window crop uses absolute position. + cropRect.offsetTo(0, 0); + cropRect.offset(cropOffsetX, cropOffsetY); + t.setCrop(mLeash, cropRect); } /** Called after animation finished. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 26bbcbb937f0..3bbeda9f0033 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -16,13 +16,11 @@ package androidx.window.extensions.embedding; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; -import android.app.ActivityThread; import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; import android.os.Binder; @@ -52,8 +50,9 @@ class TaskFragmentContainer { @NonNull private final IBinder mToken; - /** Parent leaf Task id. */ - private final int mTaskId; + /** Parent leaf Task. */ + @NonNull + private final TaskContainer mTaskContainer; /** * Server-provided task fragment information. @@ -100,14 +99,12 @@ class TaskFragmentContainer { * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. */ - TaskFragmentContainer(@Nullable Activity activity, int taskId, + TaskFragmentContainer(@Nullable Activity activity, @NonNull TaskContainer taskContainer, @NonNull SplitController controller) { mController = controller; mToken = new Binder("TaskFragmentContainer"); - if (taskId == INVALID_TASK_ID) { - throw new IllegalArgumentException("Invalid Task id"); - } - mTaskId = taskId; + mTaskContainer = taskContainer; + taskContainer.mContainers.add(this); if (activity != null) { addPendingAppearedActivity(activity); } @@ -135,9 +132,8 @@ class TaskFragmentContainer { if (mInfo == null) { return allActivities; } - ActivityThread activityThread = ActivityThread.currentActivityThread(); for (IBinder token : mInfo.getActivities()) { - Activity activity = activityThread.getActivity(token); + Activity activity = mController.getActivity(token); if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) { allActivities.add(activity); } @@ -162,9 +158,18 @@ class TaskFragmentContainer { } void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { + if (hasActivity(pendingAppearedActivity.getActivityToken())) { + return; + } + // Remove the pending activity from other TaskFragments. + mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity); mPendingAppearedActivities.add(pendingAppearedActivity); } + void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { + mPendingAppearedActivities.remove(pendingAppearedActivity); + } + boolean hasActivity(@NonNull IBinder token) { if (mInfo != null && mInfo.getActivities().contains(token)) { return true; @@ -376,7 +381,13 @@ class TaskFragmentContainer { /** Gets the parent leaf Task id. */ int getTaskId() { - return mTaskId; + return mTaskContainer.getTaskId(); + } + + /** Gets the parent Task. */ + @NonNull + TaskContainer getTaskContainer() { + return mTaskContainer; } @Override @@ -392,6 +403,7 @@ class TaskFragmentContainer { */ private String toString(boolean includeContainersToFinishOnExit) { return "TaskFragmentContainer{" + + " parentTaskId=" + getTaskId() + " token=" + mToken + " topNonFinishingActivity=" + getTopNonFinishingActivity() + " runningActivityCount=" + getRunningActivityCount() diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 7aa47ef11cb1..792a53168a0d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -113,8 +113,9 @@ public class JetpackTaskFragmentOrganizerTest { @Test public void testExpandTaskFragment() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - TASK_ID, mSplitController); + taskContainer, mSplitController); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(info); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 983208c974a1..e8d9960c4bb3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -16,28 +16,42 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import android.annotation.NonNull; import android.app.Activity; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.util.Pair; import android.window.TaskFragmentInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -50,6 +64,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -64,8 +79,8 @@ import java.util.List; public class SplitControllerTest { private static final int TASK_ID = 10; private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); + private static final float SPLIT_RATIO = 0.5f; - @Mock private Activity mActivity; @Mock private Resources mActivityResources; @@ -89,27 +104,25 @@ public class SplitControllerTest { final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); - doReturn(mActivityResources).when(mActivity).getResources(); doReturn(activityConfig).when(mActivityResources).getConfiguration(); doReturn(mHandler).when(mSplitController).getHandler(); + mActivity = createMockActivity(); } @Test public void testGetTopActiveContainer() { - TaskContainer taskContainer = new TaskContainer(TASK_ID); - // tf3 is finished so is not active. - TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); - doReturn(true).when(tf3).isFinished(); - doReturn(false).when(tf3).isWaitingActivityAppear(); + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + // tf1 has no running activity so is not active. + final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, + taskContainer, mSplitController); // tf2 has running activity so is active. - TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); + final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); - // tf1 has no running activity so is not active. - TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, TASK_ID, - mSplitController); - - taskContainer.mContainers.add(tf1); taskContainer.mContainers.add(tf2); + // tf3 is finished so is not active. + final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); + doReturn(true).when(tf3).isFinished(); + doReturn(false).when(tf3).isWaitingActivityAppear(); taskContainer.mContainers.add(tf3); mSplitController.mTaskContainers.put(TASK_ID, taskContainer); @@ -164,6 +177,18 @@ public class SplitControllerTest { } @Test + public void testOnActivityDestroyed() { + doReturn(new Binder()).when(mActivity).getActivityToken(); + final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + + assertTrue(tf.hasActivity(mActivity.getActivityToken())); + + mSplitController.onActivityDestroyed(mActivity); + + assertFalse(tf.hasActivity(mActivity.getActivityToken())); + } + + @Test public void testNewContainer() { // Must pass in a valid activity. assertThrows(IllegalArgumentException.class, () -> @@ -247,6 +272,246 @@ public class SplitControllerTest { mSplitController.updateContainer(mTransaction, tf); - verify(mSplitPresenter).updateSplitContainer(eq(splitContainer), eq(tf), eq(mTransaction)); + verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction); + } + + @Test + public void testOnActivityReparentToTask_sameProcess() { + mSplitController.onActivityReparentToTask(TASK_ID, new Intent(), + mActivity.getActivityToken()); + + // Treated as on activity created. + verify(mSplitController).onActivityCreated(mActivity); + } + + @Test + public void testOnActivityReparentToTask_diffProcess() { + // Create an empty TaskFragment to initialize for the Task. + mSplitController.newContainer(null, mActivity, TASK_ID); + final IBinder activityToken = new Binder(); + final Intent intent = new Intent(); + + mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken); + + // Treated as starting new intent + verify(mSplitController, never()).onActivityCreated(mActivity); + verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent), + isNull()); + } + + @Test + public void testResolveStartActivityIntent_withoutLaunchingActivity() { + final Intent intent = new Intent(); + final ActivityRule expandRule = new ActivityRule.Builder(r -> false, i -> i == intent) + .setShouldAlwaysExpand(true) + .build(); + mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); + + // No other activity available in the Task. + TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(mTransaction, + TASK_ID, intent, null /* launchingActivity */); + assertNull(container); + + // Task contains another activity that can be used as owner activity. + createMockTaskFragmentContainer(mActivity); + container = mSplitController.resolveStartActivityIntent(mTransaction, + TASK_ID, intent, null /* launchingActivity */); + assertNotNull(container); + } + + @Test + public void testResolveStartActivityIntent_shouldExpand() { + final Intent intent = new Intent(); + setupExpandRule(intent); + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, mActivity); + + assertNotNull(container); + assertTrue(container.areLastRequestedBoundsEqual(null)); + assertTrue(container.isLastRequestedWindowingModeEqual(WINDOWING_MODE_UNDEFINED)); + assertFalse(container.hasActivity(mActivity.getActivityToken())); + verify(mSplitPresenter).createTaskFragment(mTransaction, container.getTaskFragmentToken(), + mActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); + } + + @Test + public void testResolveStartActivityIntent_shouldSplitWithLaunchingActivity() { + final Intent intent = new Intent(); + setupSplitRule(mActivity, intent); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, mActivity); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertSplitPair(primaryContainer, container); + } + + @Test + public void testResolveStartActivityIntent_shouldSplitWithTopExpandActivity() { + final Intent intent = new Intent(); + setupSplitRule(mActivity, intent); + createMockTaskFragmentContainer(mActivity); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, null /* launchingActivity */); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertSplitPair(primaryContainer, container); + } + + @Test + public void testResolveStartActivityIntent_shouldSplitWithTopSecondaryActivity() { + final Intent intent = new Intent(); + setupSplitRule(mActivity, intent); + final Activity primaryActivity = createMockActivity(); + addSplitTaskFragments(primaryActivity, mActivity); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, null /* launchingActivity */); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertSplitPair(primaryContainer, container); + } + + @Test + public void testResolveStartActivityIntent_shouldSplitWithTopPrimaryActivity() { + final Intent intent = new Intent(); + setupSplitRule(mActivity, intent); + final Activity secondaryActivity = createMockActivity(); + addSplitTaskFragments(mActivity, secondaryActivity); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, null /* launchingActivity */); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertSplitPair(primaryContainer, container); + } + + /** Creates a mock activity in the organizer process. */ + private Activity createMockActivity() { + final Activity activity = mock(Activity.class); + doReturn(mActivityResources).when(activity).getResources(); + final IBinder activityToken = new Binder(); + doReturn(activityToken).when(activity).getActivityToken(); + doReturn(activity).when(mSplitController).getActivity(activityToken); + return activity; + } + + /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity) { + return new TaskFragmentInfo(container.getTaskFragmentToken(), + mock(WindowContainerToken.class), + new Configuration(), + 1, + true /* isVisible */, + Collections.singletonList(activity.getActivityToken()), + new Point(), + false /* isTaskClearedForReuse */, + false /* isTaskFragmentClearedForPip */); + } + + /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ + private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { + final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID); + final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity); + container.setInfo(createMockTaskFragmentInfo(container, activity)); + mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info); + return container; + } + + /** Setups a rule to always expand the given intent. */ + private void setupExpandRule(@NonNull Intent expandIntent) { + final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals) + .setShouldAlwaysExpand(true) + .build(); + mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); + } + + /** Setups a rule to always split the given activities. */ + private void setupSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryIntent) { + final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent); + mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); + } + + /** Creates a rule to always split the given activity and the given intent. */ + private SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryIntent) { + final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); + return new SplitPairRule.Builder( + activityPair -> false, + targetPair::equals, + w -> true) + .setSplitRatio(SPLIT_RATIO) + .setShouldClearTop(true) + .build(); + } + + /** Creates a rule to always split the given activities. */ + private SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity); + return new SplitPairRule.Builder( + targetPair::equals, + activityIntentPair -> false, + w -> true) + .setSplitRatio(SPLIT_RATIO) + .setShouldClearTop(true) + .build(); + } + + /** Adds a pair of TaskFragments as split for the given activities. */ + private void addSplitTaskFragments(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer( + primaryActivity); + final TaskFragmentContainer secondaryContainer = createMockTaskFragmentContainer( + secondaryActivity); + mSplitController.registerSplit( + mock(WindowContainerTransaction.class), + primaryContainer, + primaryActivity, + secondaryContainer, + createSplitRule(primaryActivity, secondaryActivity)); + + // We need to set those in case we are not respecting clear top. + // TODO(b/231845476) we should always respect clearTop. + final int windowingMode = mSplitController.getTaskContainer(TASK_ID) + .getWindowingModeForSplitTaskFragment(TASK_BOUNDS); + primaryContainer.setLastRequestedWindowingMode(windowingMode); + secondaryContainer.setLastRequestedWindowingMode(windowingMode); + primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */)); + secondaryContainer.setLastRequestedBounds(getSplitBounds(false /* isPrimary */)); + } + + /** Gets the bounds of a TaskFragment that is in split. */ + private Rect getSplitBounds(boolean isPrimary) { + final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); + return isPrimary + ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, + TASK_BOUNDS.bottom) + : new Rect(TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, + TASK_BOUNDS.bottom); + } + + /** Asserts that the two given TaskFragments are in split. */ + private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer) { + assertNotNull(primaryContainer); + assertNotNull(secondaryContainer); + assertTrue(primaryContainer.areLastRequestedBoundsEqual( + getSplitBounds(true /* isPrimary */))); + assertTrue(secondaryContainer.areLastRequestedBoundsEqual( + getSplitBounds(false /* isPrimary */))); + assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(WINDOWING_MODE_MULTI_WINDOW)); + assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual( + WINDOWING_MODE_MULTI_WINDOW)); + assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, + secondaryContainer)); } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index c40bab8d9ae3..f1042ab6ce7d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -24,8 +24,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import android.app.Activity; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; @@ -137,9 +141,8 @@ public class TaskContainerTest { assertTrue(taskContainer.isEmpty()); - final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, TASK_ID, - mController); - taskContainer.mContainers.add(tf); + final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, + taskContainer, mController); assertFalse(taskContainer.isEmpty()); @@ -148,4 +151,38 @@ public class TaskContainerTest { assertFalse(taskContainer.isEmpty()); } + + @Test + public void testGetTopTaskFragmentContainer() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + assertNull(taskContainer.getTopTaskFragmentContainer()); + + final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, + taskContainer, mController); + assertEquals(tf0, taskContainer.getTopTaskFragmentContainer()); + + final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, + taskContainer, mController); + assertEquals(tf1, taskContainer.getTopTaskFragmentContainer()); + } + + @Test + public void testGetTopNonFinishingActivity() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + assertNull(taskContainer.getTopNonFinishingActivity()); + + final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class); + taskContainer.mContainers.add(tf0); + final Activity activity0 = mock(Activity.class); + doReturn(activity0).when(tf0).getTopNonFinishingActivity(); + assertEquals(activity0, taskContainer.getTopNonFinishingActivity()); + + final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class); + taskContainer.mContainers.add(tf1); + assertEquals(activity0, taskContainer.getTopNonFinishingActivity()); + + final Activity activity1 = mock(Activity.class); + doReturn(activity1).when(tf1).getTopNonFinishingActivity(); + assertEquals(activity1, taskContainer.getTopNonFinishingActivity()); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index d80f2b939bf0..ce80cbf323b2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -76,7 +76,8 @@ public class TaskFragmentContainerTest { @Test public void testFinish() { - final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, TASK_ID, + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, taskContainer, mController); final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -107,8 +108,9 @@ public class TaskFragmentContainerTest { @Test public void testIsWaitingActivityAppear() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - TASK_ID, mController); + taskContainer, mController); assertTrue(container.isWaitingActivityAppear()); @@ -127,8 +129,9 @@ public class TaskFragmentContainerTest { @Test public void testAppearEmptyTimeout() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - TASK_ID, mController); + taskContainer, mController); assertNull(container.mAppearEmptyTimeout); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index d62dd088738d..f1ee8fa38485 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -19,6 +19,7 @@ import android.annotation.DrawableRes; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Path; @@ -350,16 +351,19 @@ public class BadgedImageView extends ConstraintLayout { } void showBadge() { - if (mBubble.getAppBadge() == null) { + Bitmap appBadgeBitmap = mBubble.getAppBadge(); + if (appBadgeBitmap == null) { mAppIcon.setVisibility(GONE); return; } + int translationX; if (mOnLeft) { - translationX = -(mBubbleIcon.getWidth() - mAppIcon.getWidth()); + translationX = -(mBubble.getBubbleIcon().getWidth() - appBadgeBitmap.getWidth()); } else { translationX = 0; } + mAppIcon.setTranslationX(translationX); mAppIcon.setVisibility(VISIBLE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index dfd4362e2373..1ea5e21a2c1e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -18,6 +18,7 @@ package com.android.wm.shell.dagger; import android.content.Context; import android.os.Handler; +import android.os.SystemClock; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -39,6 +40,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; +import com.android.wm.shell.pip.tv.TvPipBoundsController; import com.android.wm.shell.pip.tv.TvPipBoundsState; import com.android.wm.shell.pip.tv.TvPipController; import com.android.wm.shell.pip.tv.TvPipMenuController; @@ -64,6 +66,7 @@ public abstract class TvPipModule { Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, @@ -74,13 +77,13 @@ public abstract class TvPipModule { PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper windowManagerShellWrapper, - @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { + @ShellMainThread ShellExecutor mainExecutor) { return Optional.of( TvPipController.create( context, tvPipBoundsState, tvPipBoundsAlgorithm, + tvPipBoundsController, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -91,8 +94,22 @@ public abstract class TvPipModule { pipParamsChangedForwarder, displayController, windowManagerShellWrapper, - mainExecutor, - mainHandler)); + mainExecutor)); + } + + @WMSingleton + @Provides + static TvPipBoundsController provideTvPipBoundsController( + Context context, + @ShellMainThread Handler mainHandler, + TvPipBoundsState tvPipBoundsState, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm) { + return new TvPipBoundsController( + context, + SystemClock::uptimeMillis, + mainHandler, + tvPipBoundsState, + tvPipBoundsAlgorithm); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java index f8f9d6b8f8a0..65cb7ac1e5f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java @@ -16,13 +16,18 @@ package com.android.wm.shell.kidsmode; +import android.annotation.NonNull; +import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; +import java.util.Collection; + /** * A ContentObserver for listening kids mode relative setting keys: * - {@link Settings.Secure#NAVIGATION_MODE} @@ -64,7 +69,11 @@ public class KidsModeSettingsObserver extends ContentObserver { } @Override - public void onChange(boolean selfChange) { + public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags, int userId) { + if (userId != ActivityManager.getCurrentUser()) { + return; + } + if (mOnChangeRunnable != null) { mOnChangeRunnable.run(); } 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 dc703583a449..b4c87b6cbf95 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,7 +23,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import android.app.ActivityManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -87,6 +90,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { private KidsModeSettingsObserver mKidsModeSettingsObserver; private boolean mEnabled; + private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateKidsModeState(); + } + }; + DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = new DisplayController.OnDisplaysChangedListener() { @Override @@ -169,12 +179,15 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { public void initialize(StartingWindowController startingWindowController) { initStartingWindow(startingWindowController); if (mKidsModeSettingsObserver == null) { - mKidsModeSettingsObserver = new KidsModeSettingsObserver( - mMainHandler, mContext); + mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext); } mKidsModeSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState()); updateKidsModeState(); mKidsModeSettingsObserver.register(); + + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiverForAllUsers(mUserSwitchIntentReceiver, filter, null, mMainHandler); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index c0734e95ecb7..3b3091a9caf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -44,11 +44,6 @@ public interface Pip { } /** - * Hides the PIP menu. - */ - default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {} - - /** * Called when configuration is changed. */ default void onConfigurationChanged(Configuration newConfig) { @@ -125,6 +120,23 @@ public interface Pip { default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } /** + * Called when the visibility of keyguard is changed. + * @param showing {@code true} if keyguard is now showing, {@code false} otherwise. + * @param animating {@code true} if system is animating between keyguard and surface behind, + * this only makes sense when showing is {@code false}. + */ + default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { } + + /** + * Called when the dismissing animation keyguard and surfaces behind is finished. + * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}. + * + * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of + * keyguard dismiss animation. + */ + default void onKeyguardDismissAnimationFinished() { } + + /** * Dump the current state and information if need. * * @param pw The stream to dump information to. 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 4690e16bc385..45c9a958a42a 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 @@ -965,6 +965,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mDeferredAnimEndTransaction = null; } + /** Explicitly set the visibility of PiP window. */ + public void setPipVisibility(boolean visible) { + if (!isInPip()) { + return; + } + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper.alpha(tx, mLeash, visible ? 1f : 0f); + tx.apply(); + } + @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { mCurrentRotation = newConfig.windowConfiguration.getRotation(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 42ceb42f39e8..dad261ad9580 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -129,6 +129,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener = new PipControllerPinnedTaskListener(); + private boolean mIsKeyguardShowingOrAnimating; + private interface PipAnimationListener { /** * Notifies the listener that the Pip animation is started. @@ -593,6 +595,33 @@ public class PipController implements PipTransitionController.PipTransitionCallb } /** + * If {@param keyguardShowing} is {@code false} and {@param animating} is {@code true}, + * we would wait till the dismissing animation of keyguard and surfaces behind to be + * finished first to reset the visibility of PiP window. + * See also {@link #onKeyguardDismissAnimationFinished()} + */ + private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) { + if (!mPipTaskOrganizer.isInPip()) { + return; + } + if (keyguardShowing) { + mIsKeyguardShowingOrAnimating = true; + hidePipMenu(null /* onStartCallback */, null /* onEndCallback */); + mPipTaskOrganizer.setPipVisibility(false); + } else if (!animating) { + mIsKeyguardShowingOrAnimating = false; + mPipTaskOrganizer.setPipVisibility(true); + } + } + + private void onKeyguardDismissAnimationFinished() { + if (mPipTaskOrganizer.isInPip()) { + mIsKeyguardShowingOrAnimating = false; + mPipTaskOrganizer.setPipVisibility(true); + } + } + + /** * Sets a customized touch gesture that replaces the default one. */ public void setTouchGesture(PipTouchGesture gesture) { @@ -603,7 +632,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb * Sets both shelf visibility and its height. */ private void setShelfHeight(boolean visible, int height) { - setShelfHeightLocked(visible, height); + if (!mIsKeyguardShowingOrAnimating) { + setShelfHeightLocked(visible, height); + } } private void setShelfHeightLocked(boolean visible, int height) { @@ -834,13 +865,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { - mMainExecutor.execute(() -> { - PipController.this.hidePipMenu(onStartCallback, onEndCallback); - }); - } - - @Override public void expandPip() { mMainExecutor.execute(() -> { PipController.this.expandPip(); @@ -918,6 +942,18 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override + public void onKeyguardVisibilityChanged(boolean showing, boolean animating) { + mMainExecutor.execute(() -> { + PipController.this.onKeyguardVisibilityChanged(showing, animating); + }); + } + + @Override + public void onKeyguardDismissAnimationFinished() { + mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished); + } + + @Override public void dump(PrintWriter pw) { try { mMainExecutor.executeBlocking(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index 21d5d401835d..a2eadcdf6210 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -29,7 +29,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; -import android.os.SystemClock; import android.util.ArraySet; import android.util.Size; import android.view.Gravity; @@ -66,7 +65,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { @NonNull PipSnapAlgorithm pipSnapAlgorithm) { super(context, tvPipBoundsState, pipSnapAlgorithm); this.mTvPipBoundsState = tvPipBoundsState; - this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis); + this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); } @@ -80,7 +79,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding)); mKeepClearAlgorithm.setMaxRestrictedDistanceFraction( res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1)); - mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration)); } @Override @@ -104,7 +102,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true); } mTvPipBoundsState.setTvPipExpanded(isPipExpanded); - return getTvPipBounds().getBounds(); + return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); } /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @@ -114,13 +112,27 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio); } - return getTvPipBounds().getBounds(); + return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); + } + + Rect adjustBoundsForTemporaryDecor(Rect bounds) { + Rect boundsWithDecor = new Rect(bounds); + Insets decorInset = mTvPipBoundsState.getPipMenuTemporaryDecorInsets(); + Insets pipDecorReverseInsets = Insets.subtract(Insets.NONE, decorInset); + boundsWithDecor.inset(decorInset); + Gravity.apply(mTvPipBoundsState.getTvPipGravity(), + boundsWithDecor.width(), boundsWithDecor.height(), bounds, boundsWithDecor); + + // remove temporary decoration again + boundsWithDecor.inset(pipDecorReverseInsets); + return boundsWithDecor; } /** * Calculates the PiP bounds. */ - public Placement getTvPipBounds() { + @NonNull + public Placement getTvPipPlacement() { final Size pipSize = getPipSize(); final Rect displayBounds = mTvPipBoundsState.getDisplayBounds(); final Size screenSize = new Size(displayBounds.width(), displayBounds.height()); @@ -153,8 +165,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset()); mKeepClearAlgorithm.setPipPermanentDecorInsets( mTvPipBoundsState.getPipMenuPermanentDecorInsets()); - mKeepClearAlgorithm.setPipTemporaryDecorInsets( - mTvPipBoundsState.getPipMenuTemporaryDecorInsets()); final Placement placement = mKeepClearAlgorithm.calculatePipPosition( pipSize, @@ -407,8 +417,4 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { TAG, expandedSize.getWidth(), expandedSize.getHeight()); } } - - void keepUnstashedForCurrentKeepClearAreas() { - mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java new file mode 100644 index 000000000000..3a6ce81821ec --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java @@ -0,0 +1,253 @@ +/* + * 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.pip.tv; + +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Handler; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Controller managing the PiP's position. + * Manages debouncing of PiP movements and scheduling of unstashing. + */ +public class TvPipBoundsController { + private static final boolean DEBUG = false; + private static final String TAG = "TvPipBoundsController"; + + /** + * Time the calculated PiP position needs to be stable before PiP is moved there, + * to avoid erratic movement. + * Some changes will cause the PiP to be repositioned immediately, such as changes to + * unrestricted keep clear areas. + */ + @VisibleForTesting + static final long POSITION_DEBOUNCE_TIMEOUT_MILLIS = 300L; + + private final Context mContext; + private final Supplier<Long> mClock; + private final Handler mMainHandler; + private final TvPipBoundsState mTvPipBoundsState; + private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; + + @Nullable + private PipBoundsListener mListener; + + private int mResizeAnimationDuration; + private int mStashDurationMs; + private Rect mCurrentPlacementBounds; + private Rect mPipTargetBounds; + + private final Runnable mApplyPendingPlacementRunnable = this::applyPendingPlacement; + private boolean mPendingStash; + private Placement mPendingPlacement; + private int mPendingPlacementAnimationDuration; + private Runnable mUnstashRunnable; + + public TvPipBoundsController( + Context context, + Supplier<Long> clock, + Handler mainHandler, + TvPipBoundsState tvPipBoundsState, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm) { + mContext = context; + mClock = clock; + mMainHandler = mainHandler; + mTvPipBoundsState = tvPipBoundsState; + mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; + + loadConfigurations(); + } + + private void loadConfigurations() { + final Resources res = mContext.getResources(); + mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration); + mStashDurationMs = res.getInteger(R.integer.config_pipStashDuration); + } + + void setListener(PipBoundsListener listener) { + mListener = listener; + } + + /** + * Update the PiP bounds based on the state of the PiP, decors, and keep clear areas. + * Unless {@code immediate} is {@code true}, the PiP does not move immediately to avoid + * keep clear areas, but waits for a new position to stay uncontested for + * {@link #POSITION_DEBOUNCE_TIMEOUT_MILLIS} before moving to it. + * Temporary decor changes are applied immediately. + * + * @param stayAtAnchorPosition If true, PiP will be placed at the anchor position + * @param disallowStashing If true, PiP will not be placed off-screen in a stashed position + * @param animationDuration Duration of the animation to the new position + * @param immediate If true, PiP will move immediately to avoid keep clear areas + */ + @VisibleForTesting + void recalculatePipBounds(boolean stayAtAnchorPosition, boolean disallowStashing, + int animationDuration, boolean immediate) { + final Placement placement = mTvPipBoundsAlgorithm.getTvPipPlacement(); + + final int stashType = disallowStashing ? STASH_TYPE_NONE : placement.getStashType(); + mTvPipBoundsState.setStashed(stashType); + if (stayAtAnchorPosition) { + cancelScheduledPlacement(); + applyPlacementBounds(placement.getAnchorBounds(), animationDuration); + } else if (disallowStashing) { + cancelScheduledPlacement(); + applyPlacementBounds(placement.getUnstashedBounds(), animationDuration); + } else if (immediate) { + cancelScheduledPlacement(); + applyPlacementBounds(placement.getBounds(), animationDuration); + scheduleUnstashIfNeeded(placement); + } else { + applyPlacementBounds(mCurrentPlacementBounds, animationDuration); + schedulePinnedStackPlacement(placement, animationDuration); + } + } + + private void schedulePinnedStackPlacement(@NonNull final Placement placement, + int animationDuration) { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: schedulePinnedStackPlacement() - pip bounds: %s", + TAG, placement.getBounds().toShortString()); + } + + if (mPendingPlacement != null && Objects.equals(mPendingPlacement.getBounds(), + placement.getBounds())) { + mPendingStash = mPendingStash || placement.getTriggerStash(); + return; + } + + mPendingStash = placement.getStashType() != STASH_TYPE_NONE + && (mPendingStash || placement.getTriggerStash()); + + mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable); + mPendingPlacement = placement; + mPendingPlacementAnimationDuration = animationDuration; + mMainHandler.postAtTime(mApplyPendingPlacementRunnable, + mClock.get() + POSITION_DEBOUNCE_TIMEOUT_MILLIS); + } + + private void scheduleUnstashIfNeeded(final Placement placement) { + if (mUnstashRunnable != null) { + mMainHandler.removeCallbacks(mUnstashRunnable); + mUnstashRunnable = null; + } + if (placement.getUnstashDestinationBounds() != null) { + mUnstashRunnable = () -> { + applyPlacementBounds(placement.getUnstashDestinationBounds(), + mResizeAnimationDuration); + mUnstashRunnable = null; + }; + mMainHandler.postAtTime(mUnstashRunnable, mClock.get() + mStashDurationMs); + } + } + + private void applyPendingPlacement() { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: applyPendingPlacement()", TAG); + } + if (mPendingPlacement != null) { + if (mPendingStash) { + mPendingStash = false; + scheduleUnstashIfNeeded(mPendingPlacement); + } + + if (mUnstashRunnable != null) { + // currently stashed, use stashed pos + applyPlacementBounds(mPendingPlacement.getBounds(), + mPendingPlacementAnimationDuration); + } else { + applyPlacementBounds(mPendingPlacement.getUnstashedBounds(), + mPendingPlacementAnimationDuration); + } + } + + mPendingPlacement = null; + } + + void onPipDismissed() { + mCurrentPlacementBounds = null; + mPipTargetBounds = null; + cancelScheduledPlacement(); + } + + private void cancelScheduledPlacement() { + mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable); + mPendingPlacement = null; + + if (mUnstashRunnable != null) { + mMainHandler.removeCallbacks(mUnstashRunnable); + mUnstashRunnable = null; + } + } + + private void applyPlacementBounds(Rect bounds, int animationDuration) { + if (bounds == null) { + return; + } + + mCurrentPlacementBounds = bounds; + Rect adjustedBounds = mTvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(bounds); + movePipTo(adjustedBounds, animationDuration); + } + + /** Animates the PiP to the given bounds with the given animation duration. */ + private void movePipTo(Rect bounds, int animationDuration) { + if (Objects.equals(mPipTargetBounds, bounds)) { + return; + } + + mPipTargetBounds = bounds; + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString()); + } + + if (mListener != null) { + mListener.onPipTargetBoundsChange(bounds, animationDuration); + } + } + + /** + * Interface being notified of changes to the PiP bounds as calculated by + * @link TvPipBoundsController}. + */ + public interface PipBoundsListener { + /** + * Called when the calculated PiP bounds are changing. + * + * @param newTargetBounds The new bounds of the PiP. + * @param animationDuration The animation duration for the PiP movement. + */ + void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 766779413094..fa48def9c7d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -25,12 +25,10 @@ import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.app.RemoteAction; import android.app.TaskInfo; -import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; -import android.os.Handler; import android.os.RemoteException; import android.view.Gravity; @@ -46,25 +44,24 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; import java.util.Set; /** * Manages the picture-in-picture (PIP) UI and states. */ public class TvPipController implements PipTransitionController.PipTransitionCallback, - TvPipMenuController.Delegate, TvPipNotificationController.Delegate, - DisplayController.OnDisplaysChangedListener { + TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate, + TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener { private static final String TAG = "TvPipController"; static final boolean DEBUG = false; @@ -98,19 +95,18 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final TvPipBoundsState mTvPipBoundsState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; + private final TvPipBoundsController mTvPipBoundsController; private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; private final ShellExecutor mMainExecutor; - private final Handler mMainHandler; private final TvPipImpl mImpl = new TvPipImpl(); private @State int mState = STATE_NO_PIP; private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY; private int mPinnedTaskId = NONEXISTENT_TASK_ID; - private Runnable mUnstashRunnable; private RemoteAction mCloseAction; // How long the shell will wait for the app to close the PiP if a custom action is set. @@ -123,6 +119,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -133,12 +130,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, - ShellExecutor mainExecutor, - Handler mainHandler) { + ShellExecutor mainExecutor) { return new TvPipController( context, tvPipBoundsState, tvPipBoundsAlgorithm, + tvPipBoundsController, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -149,14 +146,14 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipParamsChangedForwarder, displayController, wmShell, - mainExecutor, - mainHandler).mImpl; + mainExecutor).mImpl; } private TvPipController( Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -167,16 +164,16 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, - ShellExecutor mainExecutor, - Handler mainHandler) { + ShellExecutor mainExecutor) { mContext = context; mMainExecutor = mainExecutor; - mMainHandler = mainHandler; mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; + mTvPipBoundsController = tvPipBoundsController; + mTvPipBoundsController.setListener(this); mPipMediaController = pipMediaController; @@ -227,7 +224,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal /** * Starts the process if bringing up the Pip menu if by issuing a command to move Pip * task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip - * task/window is properly positioned in {@link #onPipTransitionFinished(ComponentName, int)}. + * task/window is properly positioned in {@link #onPipTransitionFinished(int)}. */ @Override public void showPictureInPictureMenu() { @@ -256,7 +253,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: closeMenu(), state before=%s", TAG, stateToName(mState)); } setState(STATE_PIP); - mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas(); updatePinnedStackBounds(); } @@ -331,68 +327,35 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted) { if (mTvPipBoundsState.getDisplayId() == displayId) { + boolean unrestrictedAreasChanged = !Objects.equals(unrestricted, + mTvPipBoundsState.getUnrestrictedKeepClearAreas()); mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted); - updatePinnedStackBounds(); + updatePinnedStackBounds(mResizeAnimationDuration, unrestrictedAreasChanged); } } private void updatePinnedStackBounds() { - updatePinnedStackBounds(mResizeAnimationDuration); + updatePinnedStackBounds(mResizeAnimationDuration, true); } /** * Update the PiP bounds based on the state of the PiP and keep clear areas. - * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary. */ - private void updatePinnedStackBounds(int animationDuration) { + private void updatePinnedStackBounds(int animationDuration, boolean immediate) { if (mState == STATE_NO_PIP) { return; } - final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode(); final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition; - final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds(); - - int stashType = - disallowStashing ? PipBoundsState.STASH_TYPE_NONE : placement.getStashType(); - mTvPipBoundsState.setStashed(stashType); - - if (stayAtAnchorPosition) { - movePinnedStackTo(placement.getAnchorBounds()); - } else if (disallowStashing) { - movePinnedStackTo(placement.getUnstashedBounds()); - } else { - movePinnedStackTo(placement.getBounds()); - } - - if (mUnstashRunnable != null) { - mMainHandler.removeCallbacks(mUnstashRunnable); - mUnstashRunnable = null; - } - if (!disallowStashing && placement.getUnstashDestinationBounds() != null) { - mUnstashRunnable = () -> { - movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration); - }; - mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime()); - } - } - - /** Animates the PiP to the given bounds. */ - private void movePinnedStackTo(Rect bounds) { - movePinnedStackTo(bounds, mResizeAnimationDuration); + mTvPipBoundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing, + animationDuration, immediate); } - /** Animates the PiP to the given bounds with the given animation duration. */ - private void movePinnedStackTo(Rect bounds, int animationDuration) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString()); - } - mPipTaskOrganizer.scheduleAnimateResizePip(bounds, - animationDuration, rect -> { - mTvPipMenuController.updateExpansionState(); - }); - mTvPipMenuController.onPipTransitionStarted(bounds); + @Override + public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) { + mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds, + animationDuration, rect -> mTvPipMenuController.updateExpansionState()); + mTvPipMenuController.onPipTransitionStarted(newTargetBounds); } /** @@ -431,7 +394,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void closeEduText() { - updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs); + updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false); } private void registerSessionListenerForCurrentUser() { @@ -473,6 +436,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipNotificationController.dismiss(); mTvPipMenuController.closeMenu(); mTvPipBoundsState.resetTvPipState(); + mTvPipBoundsController.onPipDismissed(); setState(STATE_NO_PIP); mPinnedTaskId = NONEXISTENT_TASK_ID; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt index 07dccd58abfd..1e54436ebce9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt @@ -33,17 +33,14 @@ import kotlin.math.min import kotlin.math.roundToInt private const val DEFAULT_PIP_MARGINS = 48 -private const val DEFAULT_STASH_DURATION = 5000L private const val RELAX_DEPTH = 1 private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15 /** * This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking * into account app defined keep clear areas. - * - * @param clock A function returning a current timestamp (in milliseconds) */ -class TvPipKeepClearAlgorithm(private val clock: () -> Long) { +class TvPipKeepClearAlgorithm() { /** * Result of the positioning algorithm. * @@ -51,17 +48,17 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { * @param anchorBounds The bounds of the PiP anchor position * (where the PiP would be placed if there were no keep clear areas) * @param stashType Where the PiP has been stashed, if at all - * @param unstashDestinationBounds If stashed, the PiP should move to this position after - * [stashDuration] has passed. - * @param unstashTime If stashed, the time at which the PiP should move - * to [unstashDestinationBounds] + * @param unstashDestinationBounds If stashed, the PiP should move to this position when + * unstashing. + * @param triggerStash Whether this placement should trigger the PiP to stash, or extend + * the unstash timeout if already stashed. */ data class Placement( val bounds: Rect, val anchorBounds: Rect, @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, val unstashDestinationBounds: Rect? = null, - val unstashTime: Long = 0L + val triggerStash: Boolean = false ) { /** Bounds to use if the PiP should not be stashed. */ fun getUnstashedBounds() = unstashDestinationBounds ?: bounds @@ -79,12 +76,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { /** The distance the PiP peeks into the screen when stashed */ var stashOffset = DEFAULT_PIP_MARGINS - /** - * How long (in milliseconds) the PiP should stay stashed for after the last time the - * keep clear areas causing the PiP to stash have changed. - */ - var stashDuration = DEFAULT_STASH_DURATION - /** The fraction of screen width/height restricted keep clear areas can move the PiP */ var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION @@ -93,14 +84,10 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { private var transformedMovementBounds = Rect() private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet() - private var lastStashTime: Long = Long.MIN_VALUE /** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP * decorations are relevant for calculating intersecting keep clear areas */ private var pipPermanentDecorInsets = Insets.NONE - /** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP - * decorations are not relevant for calculating intersecting keep clear areas */ - private var pipTemporaryDecorInsets = Insets.NONE /** * Calculates the position the PiP should be placed at, taking into consideration the @@ -113,8 +100,8 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { * always try to respect these areas. * * If no free space the PiP is allowed to move to can be found, a stashed position is returned - * as [Placement.bounds], along with a position to move to once [Placement.unstashTime] has - * passed as [Placement.unstashDestinationBounds]. + * as [Placement.bounds], along with a position to move to when the PiP unstashes + * as [Placement.unstashDestinationBounds]. * * @param pipSize The size of the PiP window * @param restrictedAreas The restricted keep clear areas @@ -130,13 +117,11 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas) val pipSizeWithAllDecors = addDecors(pipSize) - val pipAnchorBoundsWithAllDecors = + val pipAnchorBoundsWithDecors = getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds) - val pipAnchorBoundsWithPermanentDecors = - removeTemporaryDecorsTransformed(pipAnchorBoundsWithAllDecors) val result = calculatePipPositionTransformed( - pipAnchorBoundsWithPermanentDecors, + pipAnchorBoundsWithDecors, transformedRestrictedAreas, transformedUnrestrictedAreas ) @@ -152,7 +137,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { anchorBounds, getStashType(pipBounds, unstashedDestBounds), unstashedDestBounds, - result.unstashTime + result.triggerStash ) } @@ -213,26 +198,13 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { !lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition) lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition - val now = clock() - if (areasOverlappingUnstashPositionChanged) { - lastStashTime = now - } - - // If overlapping areas haven't changed and the stash duration has passed, we can - // place the PiP at the unstash position - val unstashTime = lastStashTime + stashDuration - if (now >= unstashTime) { - return Placement(unstashBounds, pipAnchorBounds) - } - - // Otherwise, we'll stash it close to the unstash position val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas) return Placement( stashedBounds, pipAnchorBounds, getStashType(stashedBounds, unstashBounds), unstashBounds, - unstashTime + areasOverlappingUnstashPositionChanged ) } @@ -439,14 +411,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { } /** - * Prevents the PiP from being stashed for the current set of keep clear areas. - * The PiP may stash again if keep clear areas change. - */ - fun keepUnstashedForCurrentKeepClearAreas() { - lastStashTime = Long.MIN_VALUE - } - - /** * Updates the size of the screen. * * @param size The new size of the screen @@ -492,10 +456,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { pipPermanentDecorInsets = insets } - fun setPipTemporaryDecorInsets(insets: Insets) { - pipTemporaryDecorInsets = insets - } - /** * @param open Whether this event marks the opening of an occupied segment * @param pos The coordinate of this event @@ -790,7 +750,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { private fun addDecors(size: Size): Size { val bounds = Rect(0, 0, size.width, size.height) bounds.inset(pipPermanentDecorInsets) - bounds.inset(pipTemporaryDecorInsets) return Size(bounds.width(), bounds.height()) } @@ -805,19 +764,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { return bounds } - /** - * Removes the space that was reserved for temporary decorations around the PiP - * @param bounds the bounds (in base case) to remove the insets from - */ - private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect { - if (pipTemporaryDecorInsets == Insets.NONE) return bounds - - val reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets) - val boundsInScreenSpace = fromTransformedSpace(bounds) - boundsInScreenSpace.inset(reverseInsets) - return toTransformedSpace(boundsInScreenSpace) - } - private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) } private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 132c04481bce..4ce45e142c64 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -209,7 +209,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMovementMenuOnly()", TAG); } - mInMoveMode = true; + setInMoveMode(true); mCloseAfterExitMoveMenu = true; showMenuInternal(); } @@ -219,7 +219,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG); } - mInMoveMode = false; + setInMoveMode(false); mCloseAfterExitMoveMenu = false; showMenuInternal(); } @@ -293,6 +293,17 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return mInMoveMode; } + private void setInMoveMode(boolean moveMode) { + if (mInMoveMode == moveMode) { + return; + } + + mInMoveMode = moveMode; + if (mDelegate != null) { + mDelegate.onInMoveModeChanged(); + } + } + @Override public void onEnterMoveMode() { if (DEBUG) { @@ -300,7 +311,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode, mCloseAfterExitMoveMenu); } - mInMoveMode = true; + setInMoveMode(true); mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); } @@ -312,13 +323,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mCloseAfterExitMoveMenu); } if (mCloseAfterExitMoveMenu) { - mInMoveMode = false; + setInMoveMode(false); mCloseAfterExitMoveMenu = false; closeMenu(); return true; } if (mInMoveMode) { - mInMoveMode = false; + setInMoveMode(false); mPipMenuView.showButtonsMenu(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index d543aa742377..1d976cea96a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -980,6 +980,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, updateUnfoldBounds(); return; } + // Clear the divider remote animating flag as the divider will be re-rendered to apply + // the new rotation config. + mIsDividerRemoteAnimating = false; mSplitLayout.update(null /* t */); onLayoutSizeChanged(mSplitLayout); } @@ -1112,7 +1115,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f); mDividerFadeInAnimator.addUpdateListener(animation -> { - if (dividerLeash == null) { + if (dividerLeash == null || !dividerLeash.isValid()) { mDividerFadeInAnimator.cancel(); return; } @@ -1122,7 +1125,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - if (dividerLeash == null) { + if (dividerLeash == null || !dividerLeash.isValid()) { mDividerFadeInAnimator.cancel(); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index d89ddd2074f0..8cee4f1dc8fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -35,6 +35,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -53,6 +55,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.DisplayMetrics; import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.SurfaceControl; @@ -68,7 +71,6 @@ import com.android.internal.graphics.palette.VariationalKMeansQuantizer; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; -import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -102,7 +104,7 @@ public class SplashscreenContentDrawer { */ private static final float NO_BACKGROUND_SCALE = 192f / 160; private final Context mContext; - private final IconProvider mIconProvider; + private final HighResIconProvider mHighResIconProvider; private int mIconSize; private int mDefaultIconSize; @@ -115,12 +117,10 @@ public class SplashscreenContentDrawer { private final Handler mSplashscreenWorkerHandler; @VisibleForTesting final ColorCache mColorCache; - private final ShellExecutor mSplashScreenExecutor; - SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool, - ShellExecutor splashScreenExecutor) { + SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) { mContext = context; - mIconProvider = iconProvider; + mHighResIconProvider = new HighResIconProvider(mContext, iconProvider); mTransactionPool = pool; // Initialize Splashscreen worker thread @@ -131,7 +131,6 @@ public class SplashscreenContentDrawer { shellSplashscreenWorkerThread.start(); mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler(); mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler); - mSplashScreenExecutor = splashScreenExecutor; } /** @@ -416,18 +415,16 @@ public class SplashscreenContentDrawer { || mTmpAttrs.mIconBgColor == mThemeColor) { mFinalIconSize *= NO_BACKGROUND_SCALE; } - createIconDrawable(iconDrawable, false); + createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */); } else { final float iconScale = (float) mIconSize / (float) mDefaultIconSize; final int densityDpi = mContext.getResources().getConfiguration().densityDpi; final int scaledIconDpi = (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon"); - iconDrawable = mIconProvider.getIcon(mActivityInfo, scaledIconDpi); + iconDrawable = mHighResIconProvider.getIcon( + mActivityInfo, densityDpi, scaledIconDpi); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - if (iconDrawable == null) { - iconDrawable = mContext.getPackageManager().getDefaultActivityIcon(); - } if (!processAdaptiveIcon(iconDrawable)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "The icon is not an AdaptiveIconDrawable"); @@ -437,7 +434,8 @@ public class SplashscreenContentDrawer { scaledIconDpi, mFinalIconSize); final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - createIconDrawable(new BitmapDrawable(bitmap), true); + createIconDrawable(new BitmapDrawable(bitmap), true, + mHighResIconProvider.mLoadInDetail); } } @@ -450,14 +448,16 @@ public class SplashscreenContentDrawer { } } - private void createIconDrawable(Drawable iconDrawable, boolean legacy) { + private void createIconDrawable(Drawable iconDrawable, boolean legacy, + boolean loadInDetail) { if (legacy) { mFinalIconDrawables = SplashscreenIconDrawableFactory.makeLegacyIconDrawable( - iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler); + iconDrawable, mDefaultIconSize, mFinalIconSize, loadInDetail, + mSplashscreenWorkerHandler); } else { mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable( mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize, - mFinalIconSize, mSplashscreenWorkerHandler); + mFinalIconSize, loadInDetail, mSplashscreenWorkerHandler); } } @@ -506,11 +506,11 @@ public class SplashscreenContentDrawer { // Using AdaptiveIconDrawable here can help keep the shape consistent with the // current settings. mFinalIconSize = (int) (0.5f + mIconSize * noBgScale); - createIconDrawable(iconForeground, false); + createIconDrawable(iconForeground, false, mHighResIconProvider.mLoadInDetail); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "processAdaptiveIcon: draw whole icon"); - createIconDrawable(iconDrawable, false); + createIconDrawable(iconDrawable, false, mHighResIconProvider.mLoadInDetail); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return true; @@ -1015,4 +1015,77 @@ public class SplashscreenContentDrawer { playAnimation.run(); } } + + /** + * When loading a BitmapDrawable object with specific density, there will decode the image based + * on the density from display metrics, so even when load with higher override density, the + * final intrinsic size of a BitmapDrawable can still not big enough to draw on expect size. + * + * So here we use a standalone IconProvider object to load the Drawable object for higher + * density, and the resources object won't affect the entire system. + * + */ + private static class HighResIconProvider { + private final Context mSharedContext; + private final IconProvider mSharedIconProvider; + private boolean mLoadInDetail; + + // only create standalone icon provider when the density dpi is low. + private Context mStandaloneContext; + private IconProvider mStandaloneIconProvider; + + HighResIconProvider(Context context, IconProvider sharedIconProvider) { + mSharedContext = context; + mSharedIconProvider = sharedIconProvider; + } + + Drawable getIcon(ActivityInfo activityInfo, int currentDpi, int iconDpi) { + mLoadInDetail = false; + Drawable drawable; + if (currentDpi < iconDpi && currentDpi < DisplayMetrics.DENSITY_XHIGH) { + drawable = loadFromStandalone(activityInfo, currentDpi, iconDpi); + } else { + drawable = mSharedIconProvider.getIcon(activityInfo, iconDpi); + } + + if (drawable == null) { + drawable = mSharedContext.getPackageManager().getDefaultActivityIcon(); + } + return drawable; + } + + private Drawable loadFromStandalone(ActivityInfo activityInfo, int currentDpi, + int iconDpi) { + if (mStandaloneContext == null) { + final Configuration defConfig = mSharedContext.getResources().getConfiguration(); + mStandaloneContext = mSharedContext.createConfigurationContext(defConfig); + mStandaloneIconProvider = new IconProvider(mStandaloneContext); + } + Resources resources; + try { + resources = mStandaloneContext.getPackageManager() + .getResourcesForApplication(activityInfo.applicationInfo); + } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { + resources = null; + } + if (resources != null) { + updateResourcesDpi(resources, iconDpi); + } + final Drawable drawable = mStandaloneIconProvider.getIcon(activityInfo, iconDpi); + mLoadInDetail = true; + // reset density dpi + if (resources != null) { + updateResourcesDpi(resources, currentDpi); + } + return drawable; + } + + private void updateResourcesDpi(Resources resources, int densityDpi) { + final Configuration config = resources.getConfiguration(); + final DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + config.densityDpi = densityDpi; + displayMetrics.densityDpi = densityDpi; + resources.updateConfiguration(config, displayMetrics); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index 5f52071bf950..7f6bfd23f72b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -62,7 +62,7 @@ public class SplashscreenIconDrawableFactory { */ static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor, @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, - Handler splashscreenWorkerHandler) { + boolean loadInDetail, Handler splashscreenWorkerHandler) { Drawable foreground; Drawable background = null; boolean drawBackground = @@ -74,13 +74,13 @@ public class SplashscreenIconDrawableFactory { // If the icon is Adaptive, we already use the icon background. drawBackground = false; foreground = new ImmobileIconDrawable(foregroundDrawable, - srcIconSize, iconSize, splashscreenWorkerHandler); + srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler); } else { // Adaptive icon don't handle transparency so we draw the background of the adaptive // icon with the same color as the window background color instead of using two layers foreground = new ImmobileIconDrawable( new AdaptiveForegroundDrawable(foregroundDrawable), - srcIconSize, iconSize, splashscreenWorkerHandler); + srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler); } if (drawBackground) { @@ -91,9 +91,9 @@ public class SplashscreenIconDrawableFactory { } static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize, - int iconSize, Handler splashscreenWorkerHandler) { + int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) { return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize, - splashscreenWorkerHandler)}; + loadInDetail, splashscreenWorkerHandler)}; } /** @@ -106,11 +106,16 @@ public class SplashscreenIconDrawableFactory { private final Matrix mMatrix = new Matrix(); private Bitmap mIconBitmap; - ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, + ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) { - final float scale = (float) iconSize / srcIconSize; - mMatrix.setScale(scale, scale); - splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize)); + // This icon has lower density, don't scale it. + if (loadInDetail) { + splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize)); + } else { + final float scale = (float) iconSize / srcIconSize; + mMatrix.setScale(scale, scale); + splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize)); + } } private void preDrawIcon(Drawable drawable, int size) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 464ab1ae2a8c..54d62edf2570 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -153,8 +153,7 @@ public class StartingSurfaceDrawer { mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); mSplashScreenExecutor = splashScreenExecutor; - mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool, - mSplashScreenExecutor); + mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool); mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance()); mWindowManagerGlobal = WindowManagerGlobal.getInstance(); mDisplayManager.getDisplay(DEFAULT_DISPLAY); diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index a899709c1405..ea10be564351 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -37,6 +37,7 @@ android_test { "androidx.test.ext.junit", "androidx.dynamicanimation_dynamicanimation", "dagger2", + "frameworks-base-testutils", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "mockito-target-extended-minus-junit4", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt new file mode 100644 index 000000000000..05e472245b4a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt @@ -0,0 +1,255 @@ +/* + * 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.pip.tv + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.os.Handler +import android.os.test.TestLooper +import android.testing.AndroidTestingRunner + +import com.android.wm.shell.R +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.AdditionalAnswers.returnsFirstArg +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +class TvPipBoundsControllerTest { + val ANIMATION_DURATION = 100 + val STASH_DURATION = 5000 + val FAR_FUTURE = 60 * 60000L + val ANCHOR_BOUNDS = Rect(90, 90, 100, 100) + val STASHED_BOUNDS = Rect(99, 90, 109, 100) + val MOVED_BOUNDS = Rect(90, 80, 100, 90) + val STASHED_MOVED_BOUNDS = Rect(99, 80, 109, 90) + val ANCHOR_PLACEMENT = Placement(ANCHOR_BOUNDS, ANCHOR_BOUNDS) + val STASHED_PLACEMENT = Placement(STASHED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, ANCHOR_BOUNDS, false) + val STASHED_PLACEMENT_RESTASH = Placement(STASHED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, ANCHOR_BOUNDS, true) + val MOVED_PLACEMENT = Placement(MOVED_BOUNDS, ANCHOR_BOUNDS) + val STASHED_MOVED_PLACEMENT = Placement(STASHED_MOVED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, MOVED_BOUNDS, false) + val STASHED_MOVED_PLACEMENT_RESTASH = Placement(STASHED_MOVED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, MOVED_BOUNDS, true) + + lateinit var boundsController: TvPipBoundsController + var time = 0L + lateinit var testLooper: TestLooper + lateinit var mainHandler: Handler + + var inMenu = false + var inMoveMode = false + + @Mock + lateinit var context: Context + @Mock + lateinit var resources: Resources + @Mock + lateinit var tvPipBoundsState: TvPipBoundsState + @Mock + lateinit var tvPipBoundsAlgorithm: TvPipBoundsAlgorithm + @Mock + lateinit var listener: TvPipBoundsController.PipBoundsListener + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + time = 0L + inMenu = false + inMoveMode = false + + testLooper = TestLooper { time } + mainHandler = Handler(testLooper.getLooper()) + + whenever(context.resources).thenReturn(resources) + whenever(resources.getInteger(R.integer.config_pipStashDuration)).thenReturn(STASH_DURATION) + whenever(tvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(any())) + .then(returnsFirstArg<Rect>()) + + boundsController = TvPipBoundsController( + context, + { time }, + mainHandler, + tvPipBoundsState, + tvPipBoundsAlgorithm) + boundsController.setListener(listener) + } + + @Test + fun testPlacement_MovedAfterDebounceTimeout() { + triggerPlacement(MOVED_PLACEMENT) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test + fun testStashedPlacement_MovedAfterDebounceTimeout_Unstashes() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testDebounceSamePlacement_MovesDebounceTimeoutAfterFirstPlacement() { + triggerPlacement(MOVED_PLACEMENT) + advanceTimeTo(POSITION_DEBOUNCE_TIMEOUT_MILLIS / 2) + triggerPlacement(MOVED_PLACEMENT) + + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) + } + + @Test + fun testNoMovementUntilPlacementStabilizes() { + triggerPlacement(ANCHOR_PLACEMENT) + advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) + triggerPlacement(MOVED_PLACEMENT) + advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) + triggerPlacement(ANCHOR_PLACEMENT) + advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) + triggerPlacement(MOVED_PLACEMENT) + + assertMovementAt(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) + } + + @Test + fun testUnstashIfStashNoLongerNecessary() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + + triggerPlacement(ANCHOR_PLACEMENT) + assertMovementAt(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS, ANCHOR_BOUNDS) + } + + @Test + fun testRestashingPlacementDelaysUnstash() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + + assertNoMovementUpTo(time + STASH_DURATION / 2) + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertNoMovementUpTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS) + assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testNonRestashingPlacementDoesNotDelayUnstash() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + + assertNoMovementUpTo(time + STASH_DURATION / 2) + triggerPlacement(STASHED_PLACEMENT) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testImmediatePlacement() { + triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH) + assertMovement(STASHED_BOUNDS) + assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testInMoveMode_KeepAtAnchor() { + startMoveMode() + triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) + assertMovement(ANCHOR_BOUNDS) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test + fun testInMenu_Unstashed() { + openPipMenu() + triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) + assertMovement(MOVED_BOUNDS) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test + fun testCloseMenu_DoNotRestash() { + openPipMenu() + triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) + assertMovement(MOVED_BOUNDS) + + closePipMenu() + triggerPlacement(STASHED_MOVED_PLACEMENT) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + fun assertMovement(bounds: Rect) { + verify(listener).onPipTargetBoundsChange(eq(bounds), anyInt()) + reset(listener) + } + + fun assertMovementAt(timeMs: Long, bounds: Rect) { + assertNoMovementUpTo(timeMs - 1) + advanceTimeTo(timeMs) + assertMovement(bounds) + } + + fun assertNoMovementUpTo(timeMs: Long) { + advanceTimeTo(timeMs) + verify(listener, never()).onPipTargetBoundsChange(any(), anyInt()) + } + + fun triggerPlacement(placement: Placement, immediate: Boolean = false) { + whenever(tvPipBoundsAlgorithm.getTvPipPlacement()).thenReturn(placement) + val stayAtAnchorPosition = inMoveMode + val disallowStashing = inMenu || stayAtAnchorPosition + boundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing, + ANIMATION_DURATION, immediate) + } + + fun triggerImmediatePlacement(placement: Placement) { + triggerPlacement(placement, true) + } + + fun openPipMenu() { + inMenu = true + inMoveMode = false + } + + fun closePipMenu() { + inMenu = false + inMoveMode = false + } + + fun startMoveMode() { + inMenu = true + inMoveMode = true + } + + fun advanceTimeTo(ms: Long) { + time = ms + testLooper.dispatchAll() + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt index 46f388d0ce0e..0fcc5cf384c9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt @@ -30,7 +30,9 @@ import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement import org.junit.Before import org.junit.Test import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue @RunWith(AndroidTestingRunner::class) class TvPipKeepClearAlgorithmTest { @@ -46,7 +48,6 @@ class TvPipKeepClearAlgorithmTest { private lateinit var pipSize: Size private lateinit var movementBounds: Rect private lateinit var algorithm: TvPipKeepClearAlgorithm - private var currentTime = 0L private var restrictedAreas = mutableSetOf<Rect>() private var unrestrictedAreas = mutableSetOf<Rect>() private var gravity: Int = 0 @@ -58,16 +59,14 @@ class TvPipKeepClearAlgorithmTest { restrictedAreas.clear() unrestrictedAreas.clear() - currentTime = 0L pipSize = DEFAULT_PIP_SIZE gravity = Gravity.BOTTOM or Gravity.RIGHT - algorithm = TvPipKeepClearAlgorithm({ currentTime }) + algorithm = TvPipKeepClearAlgorithm() algorithm.setScreenSize(SCREEN_SIZE) algorithm.setMovementBounds(movementBounds) algorithm.pipAreaPadding = PADDING algorithm.stashOffset = STASH_OFFSET - algorithm.stashDuration = 5000L algorithm.setGravity(gravity) algorithm.maxRestrictedDistanceFraction = 0.3 } @@ -265,7 +264,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_BOTTOM, placement.stashType) assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) + assertTrue(placement.triggerStash) } @Test @@ -305,7 +304,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) + assertTrue(placement.triggerStash) } @Test @@ -352,9 +351,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) - - currentTime += 1000 + assertTrue(placement.triggerStash) restrictedAreas.remove(sideBar) placement = getActualPlacement() @@ -363,7 +360,7 @@ class TvPipKeepClearAlgorithmTest { } @Test - fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() { + fun test_Stashed_UnstashBoundsStaysObstructed_DoesNotTriggerStash() { gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -384,13 +381,13 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) - - currentTime += algorithm.stashDuration + assertTrue(placement.triggerStash) placement = getActualPlacement() - assertEquals(expectedUnstashBounds, placement.bounds) - assertNotStashed(placement) + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertFalse(placement.triggerStash) } @Test @@ -415,9 +412,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) - - currentTime += 1000 + assertTrue(placement.triggerStash) val newObstruction = Rect( 0, @@ -431,7 +426,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime) + assertTrue(placement.triggerStash) } @Test @@ -458,21 +453,9 @@ class TvPipKeepClearAlgorithmTest { @Test fun test_PipInsets() { - val permInsets = Insets.of(-1, -2, -3, -4) - algorithm.setPipPermanentDecorInsets(permInsets) - testInsetsForAllPositions(permInsets) - - val tempInsets = Insets.of(-4, -3, -2, -1) - algorithm.setPipPermanentDecorInsets(Insets.NONE) - algorithm.setPipTemporaryDecorInsets(tempInsets) - testInsetsForAllPositions(tempInsets) - - algorithm.setPipPermanentDecorInsets(permInsets) - algorithm.setPipTemporaryDecorInsets(tempInsets) - testInsetsForAllPositions(Insets.add(permInsets, tempInsets)) - } + val insets = Insets.of(-1, -2, -3, -4) + algorithm.setPipPermanentDecorInsets(insets) - private fun testInsetsForAllPositions(insets: Insets) { gravity = Gravity.BOTTOM or Gravity.RIGHT testAnchorPositionWithInsets(insets) @@ -546,6 +529,6 @@ class TvPipKeepClearAlgorithmTest { private fun assertNotStashed(actual: Placement) { assertEquals(STASH_TYPE_NONE, actual.stashType) assertNull(actual.unstashDestinationBounds) - assertEquals(0L, actual.unstashTime) + assertFalse(actual.triggerStash) } } diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp index 4826d5a0c8da..078041411a21 100644 --- a/libs/hwui/AnimatorManager.cpp +++ b/libs/hwui/AnimatorManager.cpp @@ -31,7 +31,8 @@ static void detach(sp<BaseRenderNodeAnimator>& animator) { animator->detach(); } -AnimatorManager::AnimatorManager(RenderNode& parent) : mParent(parent), mAnimationHandle(nullptr) {} +AnimatorManager::AnimatorManager(RenderNode& parent) + : mParent(parent), mAnimationHandle(nullptr), mCancelAllAnimators(false) {} AnimatorManager::~AnimatorManager() { for_each(mNewAnimators.begin(), mNewAnimators.end(), detach); @@ -82,8 +83,16 @@ void AnimatorManager::pushStaging() { } mNewAnimators.clear(); } - for (auto& animator : mAnimators) { - animator->pushStaging(mAnimationHandle->context()); + + if (mCancelAllAnimators) { + for (auto& animator : mAnimators) { + animator->forceEndNow(mAnimationHandle->context()); + } + mCancelAllAnimators = false; + } else { + for (auto& animator : mAnimators) { + animator->pushStaging(mAnimationHandle->context()); + } } } @@ -184,5 +193,9 @@ void AnimatorManager::endAllActiveAnimators() { mAnimationHandle->release(); } +void AnimatorManager::forceEndAnimators() { + mCancelAllAnimators = true; +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h index a0df01d5962c..6002661dc82a 100644 --- a/libs/hwui/AnimatorManager.h +++ b/libs/hwui/AnimatorManager.h @@ -16,11 +16,11 @@ #ifndef ANIMATORMANAGER_H #define ANIMATORMANAGER_H -#include <vector> - #include <cutils/compiler.h> #include <utils/StrongPointer.h> +#include <vector> + #include "utils/Macros.h" namespace android { @@ -56,6 +56,8 @@ public: // Hard-ends all animators. May only be called on the UI thread. void endAllStagingAnimators(); + void forceEndAnimators(); + // Hard-ends all animators that have been pushed. Used for cleanup if // the ActivityContext is being destroyed void endAllActiveAnimators(); @@ -71,6 +73,8 @@ private: // To improve the efficiency of resizing & removing from the vector std::vector<sp<BaseRenderNodeAnimator> > mNewAnimators; std::vector<sp<BaseRenderNodeAnimator> > mAnimators; + + bool mCancelAllAnimators; }; } /* namespace uirenderer */ diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 944393c63ad6..db7639029187 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -543,6 +543,12 @@ static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz, renderNode->animators().endAllStagingAnimators(); } +static void android_view_RenderNode_forceEndAnimators(JNIEnv* env, jobject clazz, + jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->animators().forceEndAnimators(); +} + // ---------------------------------------------------------------------------- // SurfaceView position callback // ---------------------------------------------------------------------------- @@ -745,6 +751,7 @@ static const JNINativeMethod gMethods[] = { {"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize}, {"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator}, {"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators}, + {"nForceEndAnimators", "(J)V", (void*)android_view_RenderNode_forceEndAnimators}, {"nRequestPositionUpdates", "(JLjava/lang/ref/WeakReference;)V", (void*)android_view_RenderNode_requestPositionUpdates}, diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java index 11cacd01f53d..fe58cca9395f 100644 --- a/media/java/android/media/AudioDeviceVolumeManager.java +++ b/media/java/android/media/AudioDeviceVolumeManager.java @@ -235,13 +235,7 @@ public class AudioDeviceVolumeManager { mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub(); } } else { - for (ListenerInfo info : mDeviceVolumeListeners) { - if (info.mListener == vclistener) { - throw new IllegalArgumentException( - "attempt to call setDeviceAbsoluteMultiVolumeBehavior() " - + "on a previously registered listener"); - } - } + mDeviceVolumeListeners.removeIf(info -> info.mDevice.equalTypeAddress(device)); } mDeviceVolumeListeners.add(listenerInfo); mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment); @@ -304,6 +298,28 @@ public class AudioDeviceVolumeManager { "removeOnDeviceVolumeBehaviorChangedListener"); } + /** + * Return human-readable name for volume behavior + * @param behavior one of the volume behaviors defined in AudioManager + * @return a string for the given behavior + */ + public static String volumeBehaviorName(@AudioManager.DeviceVolumeBehavior int behavior) { + switch (behavior) { + case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE: + return "DEVICE_VOLUME_BEHAVIOR_VARIABLE"; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL: + return "DEVICE_VOLUME_BEHAVIOR_FULL"; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED: + return "DEVICE_VOLUME_BEHAVIOR_FIXED"; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: + return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE"; + case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: + return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE"; + default: + return "invalid volume behavior " + behavior; + } + } + private final class DeviceVolumeBehaviorDispatcherStub extends IDeviceVolumeBehaviorDispatcher.Stub implements CallbackUtil.DispatcherStub { public void register(boolean register) { diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 55c558f36880..85cd342b5e11 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2910,6 +2910,10 @@ public class AudioTrack extends PlayerBase * For portability, an application should prime the data path to the maximum allowed * by writing data until the write() method returns a short transfer count. * This allows play() to start immediately, and reduces the chance of underrun. + *<p> + * As of {@link android.os.Build.VERSION_CODES#S} the minimum level to start playing + * can be obtained using {@link #getStartThresholdInFrames()} and set with + * {@link #setStartThresholdInFrames(int)}. * * @throws IllegalStateException if the track isn't properly initialized */ diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index ef0270b5414c..e5673a613b59 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -1728,7 +1728,9 @@ public class Tuner implements AutoCloseable { } int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber); if (res == RESULT_SUCCESS) { - // TODO: b/211778848 Update Tuner Resource Manager. + if (!mTunerResourceManager.setMaxNumberOfFrontends(frontendType, maxNumber)) { + res = RESULT_INVALID_ARGUMENT; + } } return res; } @@ -1749,7 +1751,13 @@ public class Tuner implements AutoCloseable { TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { return -1; } - return nativeGetMaxNumberOfFrontends(frontendType); + int maxNumFromHAL = nativeGetMaxNumberOfFrontends(frontendType); + int maxNumFromTRM = mTunerResourceManager.getMaxNumberOfFrontends(frontendType); + if (maxNumFromHAL != maxNumFromTRM) { + Log.w(TAG, "max num of usable frontend is out-of-sync b/w " + maxNumFromHAL + + " != " + maxNumFromTRM); + } + return maxNumFromHAL; } /** @hide */ diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index 5ada89e9dea7..15175a783924 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -401,6 +401,43 @@ public class TunerResourceManager { } /** + * Sets the maximum usable frontends number of a given frontend type. It is used to enable or + * disable frontends when cable connection status is changed by user. + * + * @param frontendType the frontendType which the maximum usable number will be set for. + * @param maxNum the new maximum usable number. + * + * @return true if successful and false otherwise. + */ + public boolean setMaxNumberOfFrontends(int frontendType, int maxNum) { + boolean result = false; + try { + result = mService.setMaxNumberOfFrontends(frontendType, maxNum); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return result; + } + + /** + * Get the maximum usable frontends number of a given frontend type. + * + * @param frontendType the frontendType which the maximum usable number will be queried for. + * + * @return the maximum usable number of the queried frontend type. Returns -1 when the + * frontendType is invalid + */ + public int getMaxNumberOfFrontends(int frontendType) { + int result = -1; + try { + result = mService.getMaxNumberOfFrontends(frontendType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return result; + } + + /** * Requests from the client to share frontend with an existing client. * * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index d16fc6ca1dc7..144b98c36655 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -166,6 +166,27 @@ interface ITunerResourceManager { boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle); /* + * Sets the maximum usable frontends number of a given frontend type. It is used to enable or + * disable frontends when cable connection status is changed by user. + * + * @param frontendType the frontendType which the maximum usable number will be set for. + * @param maxNumber the new maximum usable number. + * + * @return true if successful and false otherwise. + */ + boolean setMaxNumberOfFrontends(in int frontendType, in int maxNum); + + /* + * Get the maximum usable frontends number of a given frontend type. + * + * @param frontendType the frontendType which the maximum usable number will be queried for. + * + * @return the maximum usable number of the queried frontend type. Returns -1 when the + * frontendType is invalid + */ + int getMaxNumberOfFrontends(in int frontendType); + + /* * Requests to share frontend with an existing client. * * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp index f4a39b3469bb..b7ad6dcf9354 100644 --- a/media/jni/android_media_Utils.cpp +++ b/media/jni/android_media_Utils.cpp @@ -18,10 +18,10 @@ #define LOG_TAG "AndroidMediaUtils" #include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h> -#include <hardware/camera3.h> #include <ui/GraphicBufferMapper.h> #include <ui/GraphicTypes.h> #include <utils/Log.h> + #include "android_media_Utils.h" #define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) ) @@ -122,8 +122,8 @@ uint32_t Image_getBlobSize(LockedImage* buffer, bool usingRGBAOverride) { } // First check for BLOB transport header at the end of the buffer - uint8_t* header = blobBuffer + (width - sizeof(struct camera3_jpeg_blob)); - struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header); + uint8_t* header = blobBuffer + (width - sizeof(struct camera3_jpeg_blob_v2)); + struct camera3_jpeg_blob_v2 *blob = (struct camera3_jpeg_blob_v2*)(header); if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID || blob->jpeg_blob_id == CAMERA3_HEIC_BLOB_ID) { size = blob->jpeg_size; diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h index 4feb4f516f1e..c12cec129ede 100644 --- a/media/jni/android_media_Utils.h +++ b/media/jni/android_media_Utils.h @@ -50,6 +50,20 @@ int getBufferWidth(BufferItem *buffer); int getBufferHeight(BufferItem *buffer); +// Must be in sync with AIDL CameraBlob : android.hardware.camera.device.CameraBlob +// HALs must NOT copy this definition. +// for details: http://b/229688810 +typedef struct camera3_jpeg_blob_v2 { + uint32_t jpeg_blob_id; + uint32_t jpeg_size; +} camera3_jpeg_blobv2_t; + +// Must be in sync with AIDL CameraBlob : android.hardware.camera.device.CameraBlobId +enum { + CAMERA3_JPEG_BLOB_ID = 0x00FF, + CAMERA3_JPEG_APP_SEGMENTS_BLOB_ID = 0x0100, +}; + }; // namespace android #endif // _ANDROID_MEDIA_UTILS_H_ diff --git a/packages/BackupRestoreConfirmation/res/values-eu/strings.xml b/packages/BackupRestoreConfirmation/res/values-eu/strings.xml index 5b522787032c..6f734a37cdae 100644 --- a/packages/BackupRestoreConfirmation/res/values-eu/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-eu/strings.xml @@ -18,10 +18,10 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="backup_confirm_title" msgid="827563724209303345">"Babeskopia osoa"</string> <string name="restore_confirm_title" msgid="5469365809567486602">"Leheneratze osoa"</string> - <string name="backup_confirm_text" msgid="1878021282758896593">"Datu guztien babeskopia egitea eta konektatutako ordenagailu batean gordetzea eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu babeskopia egitea zuk eskatu, ez eman eragiketarekin jarraitzeko baimena."</string> + <string name="backup_confirm_text" msgid="1878021282758896593">"Datu guztien babeskopia egitea eta konektatutako ordenagailu batean gordetzea eskatu da. Horretarako baimena eman nahi duzu?\n\nBabeskopia egitea zeuk eskatu ez baduzu, ez eman eragiketarekin jarraitzeko baimena."</string> <string name="allow_backup_button_label" msgid="4217228747769644068">"Egin datuen babeskopia"</string> <string name="deny_backup_button_label" msgid="6009119115581097708">"Ez egin babeskopia"</string> - <string name="restore_confirm_text" msgid="7499866728030461776">"Konektatutako ordenagailu bateko datu guztiak leheneratzeko eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu leheneratzea zuk eskatu, ez eman eragiketarekin jarraitzeko baimena. Eragiketa gauzatzen bada, gailuan dituzun datu guztiak ordeztuko dira!"</string> + <string name="restore_confirm_text" msgid="7499866728030461776">"Konektatutako ordenagailu bateko datu guztiak leheneratzeko eskatu da. Horretarako baimena eman nahi duzu?\n\nLeheneratzea zeuk eskatu ez baduzu, ez eman eragiketarekin jarraitzeko baimena. Eragiketa gauzatzen bada, gailuan dituzun datu guztiak ordeztuko dira!"</string> <string name="allow_restore_button_label" msgid="3081286752277127827">"Leheneratu datuak"</string> <string name="deny_restore_button_label" msgid="1724367334453104378">"Ez leheneratu"</string> <string name="current_password_text" msgid="8268189555578298067">"Idatzi babeskopien oraingo pasahitza behean:"</string> diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 8b5d214f7a10..16cd2e55136c 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -46,6 +46,7 @@ android:launchMode="singleInstance" android:excludeFromRecents="true" android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE" + android:configChanges="orientation|screenSize" android:theme="@style/ChooserActivity"/> <service diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml new file mode 100644 index 000000000000..8d7fa26acff4 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@android:color/system_accent1_200"> + <path + android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z" + android:fillColor="#3C4043" + android:fillType="evenOdd"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml new file mode 100644 index 000000000000..5689e34d0886 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml @@ -0,0 +1,25 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@android:color/system_accent1_200"> + <path android:fillColor="@android:color/white" + android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml new file mode 100644 index 000000000000..06bfad5b8efd --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@android:color/system_accent1_200"> + <path android:fillColor="@android:color/white" + android:pathData="M4,19V17H6V10Q6,7.925 7.25,6.312Q8.5,4.7 10.5,4.2V3.5Q10.5,2.875 10.938,2.438Q11.375,2 12,2Q12.625,2 13.062,2.438Q13.5,2.875 13.5,3.5V4.2Q15.5,4.7 16.75,6.312Q18,7.925 18,10V17H20V19ZM12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20H14Q14,20.825 13.413,21.413Q12.825,22 12,22ZM8,17H16V10Q16,8.35 14.825,7.175Q13.65,6 12,6Q10.35,6 9.175,7.175Q8,8.35 8,10Z"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml new file mode 100644 index 000000000000..f8aef33c88ea --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml @@ -0,0 +1,25 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@android:color/system_accent1_200"> + <path android:fillColor="@android:color/white" + android:pathData="M6,17H18L14.25,12L11.25,16L9,13ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19ZM5,5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Q5,19 5,19Q5,19 5,19V5Q5,5 5,5Q5,5 5,5Z"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml b/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml index 8e92051faf6c..6ce1f1263638 100644 --- a/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml +++ b/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml @@ -18,6 +18,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@android:color/system_accent1_100"/> - <corners android:topLeftRadius="16dp" android:topRightRadius="16dp" - android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/> + <corners android:topLeftRadius="20dp" android:topRightRadius="20dp" + android:bottomLeftRadius="20dp" android:bottomRightRadius="20dp"/> </shape>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable/ic_apps.xml index d1ec8637775c..7295e78b16cb 100644 --- a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml +++ b/packages/CompanionDeviceManager/res/drawable/ic_apps.xml @@ -19,7 +19,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24"> + android:viewportHeight="24" + android:tint="@android:color/system_accent1_600"> <path android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z" android:fillColor="#3C4043" diff --git a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml index e5825bcbf70c..7b1ef85ae5fd 100644 --- a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml +++ b/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml @@ -20,7 +20,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="@android:color/system_accent1_600"> <path android:fillColor="@android:color/white" android:pathData="M4,19V17H6V10Q6,7.925 7.25,6.312Q8.5,4.7 10.5,4.2V3.5Q10.5,2.875 10.938,2.438Q11.375,2 12,2Q12.625,2 13.062,2.438Q13.5,2.875 13.5,3.5V4.2Q15.5,4.7 16.75,6.312Q18,7.925 18,10V17H20V19ZM12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20H14Q14,20.825 13.413,21.413Q12.825,22 12,22ZM8,17H16V10Q16,8.35 14.825,7.175Q13.65,6 12,6Q10.35,6 9.175,7.175Q8,8.35 8,10Z"/> </vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable/ic_storage.xml index 406a3b5dada5..3e033d372e72 100644 --- a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml +++ b/packages/CompanionDeviceManager/res/drawable/ic_storage.xml @@ -20,7 +20,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="@android:color/system_accent1_600"> <path android:fillColor="@android:color/white" android:pathData="M6,17H18L14.25,12L11.25,16L9,13ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19ZM5,5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Q5,19 5,19Q5,19 5,19V5Q5,5 5,5Q5,5 5,5Z"/> </vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index c0e6c09d69ed..520ade892f51 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -12,135 +12,142 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/activity_confirmation" +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:minWidth="340dp"> + android:layout_height="match_parent" + style="@style/ScrollViewStyle"> - <LinearLayout android:id="@+id/association_confirmation" - style="@style/ContainerLayout"> + <LinearLayout + android:id="@+id/activity_confirmation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false"> - <!-- A header for selfManaged devices only. --> - <include layout="@layout/vendor_header" /> + <LinearLayout android:id="@+id/association_confirmation" + style="@style/ContainerLayout"> - <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. --> + <!-- A header for selfManaged devices only. --> + <include layout="@layout/vendor_header" /> - <ImageView - android:id="@+id/profile_icon" - android:layout_width="match_parent" - android:layout_height="32dp" - android:gravity="center" - android:layout_marginTop="18dp" - android:tint="@android:color/system_accent1_600"/> + <!-- Do NOT change the ID of the root LinearLayout above: + it's referenced in CTS tests. --> - <LinearLayout style="@style/Description"> - <TextView - android:id="@+id/title" - style="@style/DescriptionTitle" /> + <ImageView + android:id="@+id/profile_icon" + android:layout_width="match_parent" + android:layout_height="32dp" + android:gravity="center" + android:layout_marginTop="18dp" + android:tint="@android:color/system_accent1_600"/> - <TextView - android:id="@+id/summary" - style="@style/DescriptionSummary" /> + <LinearLayout style="@style/Description"> + <TextView + android:id="@+id/title" + style="@style/DescriptionTitle" /> - </LinearLayout> + <TextView + android:id="@+id/summary" + style="@style/DescriptionSummary" /> - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1"> + </LinearLayout> - <LinearLayout - android:id="@+id/multiple_device_list" + <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:orientation="vertical" - android:visibility="gone"> + android:layout_height="0dp" + android:layout_weight="1"> + + <LinearLayout + android:id="@+id/multiple_device_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:orientation="vertical" + android:visibility="gone"> + + <View + android:id="@+id/border_top" + style="@style/DeviceListBorder" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/device_list" + android:layout_width="match_parent" + android:scrollbars="vertical" + android:layout_marginBottom="12dp" + android:layout_height="200dp" /> - <View - android:id="@+id/border_top" - style="@style/DeviceListBorder" /> + <View + android:id="@+id/border_bottom" + style="@style/DeviceListBorder" /> + + </LinearLayout> <androidx.recyclerview.widget.RecyclerView - android:id="@+id/device_list" + android:id="@+id/permission_list" android:layout_width="match_parent" - android:scrollbars="vertical" - android:layout_marginBottom="12dp" - android:layout_height="200dp" /> + android:layout_height="wrap_content" /> - <View - android:id="@+id/border_bottom" - style="@style/DeviceListBorder" /> + <ProgressBar + android:id="@+id/spinner_multiple_device" + android:visibility="gone" + style="@style/Spinner" /> - </LinearLayout> + </RelativeLayout> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/permission_list" + <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical" + android:layout_marginTop="16dp"> - <ProgressBar - android:id="@+id/spinner_multiple_device" - android:visibility="gone" - style="@style/Spinner" /> + <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> - </RelativeLayout> + <Button + android:id="@+id/btn_positive" + style="@style/PositiveButton" + android:text="@string/consent_yes" /> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="vertical" - android:layout_marginTop="16dp"> + <Button + android:id="@+id/btn_negative" + android:layout_marginBottom="12dp" + style="@style/NegativeButton" + android:text="@string/consent_no" /> - <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="bottom|end" + android:orientation="vertical" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp"> - <Button - android:id="@+id/btn_positive" - style="@style/PositiveButton" - android:text="@string/consent_yes" /> + <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> - <Button - android:id="@+id/btn_negative" - android:layout_marginBottom="12dp" - style="@style/NegativeButton" - android:text="@string/consent_no" /> + <Button + android:id="@+id/btn_negative_multiple_devices" + style="@style/NegativeButtonMultipleDevices" + android:textColor="?android:textColorPrimary" + android:visibility="gone" + android:text="@string/consent_no" /> + </LinearLayout> </LinearLayout> - <LinearLayout + <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="bottom|right" - android:orientation="vertical" - android:layout_marginRight="16dp" - android:layout_marginBottom="16dp"> - - <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + android:layout_height="match_parent" + android:layout_weight="1"> - <Button - android:id="@+id/btn_negative_multiple_devices" - style="@style/NegativeButtonMultipleDevices" - android:textColor="?android:textColorPrimary" + <ProgressBar + android:id="@+id/spinner_single_device" android:visibility="gone" - android:text="@string/consent_no" /> - </LinearLayout> + style="@style/Spinner" /> + </RelativeLayout>> </LinearLayout> - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1"> - - <ProgressBar - android:id="@+id/spinner_single_device" - android:visibility="gone" - style="@style/Spinner" /> - </RelativeLayout>> - -</LinearLayout>
\ No newline at end of file +</ScrollView>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml index a9ace44d49b8..d0d46f715846 100644 --- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml @@ -15,54 +15,67 @@ ~ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/helper_confirmation" - android:theme="@style/ChooserActivity" - android:padding="12dp" - style="@style/ContainerLayout"> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="@style/ScrollViewStyle"> - <ImageView - android:id="@+id/app_icon" + <LinearLayout android:layout_width="match_parent" - android:layout_height="32dp" - android:gravity="center" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp"/> + android:layout_height="wrap_content"> - <TextView - android:id="@+id/helper_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:paddingHorizontal="12dp" - android:textColor="?android:attr/textColorPrimary" - android:textSize="22sp" /> + <LinearLayout + android:id="@+id/helper_confirmation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/ChooserActivity" + android:padding="12dp" + style="@style/ContainerLayout"> - <TextView - android:id="@+id/helper_summary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="24dp" - android:layout_marginRight="24dp" - android:layout_marginTop="12dp" - android:layout_marginBottom="24dp" - android:gravity="center" - android:textColor="?android:attr/textColorSecondary" - android:textSize="14sp" /> + <ImageView + android:id="@+id/app_icon" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp"/> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_marginRight="12dp" - android:layout_marginBottom="12dp" - android:gravity="end"> + <TextView + android:id="@+id/helper_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary" + android:textSize="22sp" /> + + <TextView + android:id="@+id/helper_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp" + android:layout_marginTop="12dp" + android:layout_marginBottom="32dp" + android:gravity="center" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginEnd="12dp" + android:gravity="end"> + + <Button + android:id="@+id/btn_back" + style="@style/VendorHelperBackButton" + android:text="@string/consent_back" /> + + </LinearLayout> - <Button - android:id="@+id/btn_back" - style="@style/VendorHelperBackButton" - android:text="@string/consent_back" /> + </LinearLayout> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</ScrollView>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml index eeb988f364b2..0a5afe4363ac 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml @@ -28,14 +28,15 @@ android:id="@android:id/icon" android:layout_width="24dp" android:layout_height="24dp" - android:layout_marginLeft="24dp" - android:layout_marginRight="12dp" + android:layout_marginStart="24dp" android:tint="@android:color/system_accent1_600"/> <TextView android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingStart="24dp" + android:paddingEnd="24dp" android:singleLine="true" android:textAppearance="?android:attr/textAppearanceListItemSmall"/> diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml index 3dce38d0dc20..54916a24b7df 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml @@ -20,8 +20,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingLeft="32dp" - android:paddingRight="32dp" + android:paddingStart="32dp" + android:paddingEnd="32dp" android:paddingBottom="14dp"> <ImageView @@ -30,7 +30,6 @@ android:layout_height="24dp" android:layout_marginTop="8dp" android:layout_marginEnd="12dp" - android:tint="@android:color/system_accent1_600" android:contentDescription="Permission Icon"/> <LinearLayout @@ -51,7 +50,6 @@ android:id="@+id/permission_summary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingRight="24dp" android:textSize="14sp" android:textColor="?android:attr/textColorSecondary"/> diff --git a/packages/CompanionDeviceManager/res/layout/vendor_header.xml b/packages/CompanionDeviceManager/res/layout/vendor_header.xml index c35f59e7e8a0..14e74312dd2c 100644 --- a/packages/CompanionDeviceManager/res/layout/vendor_header.xml +++ b/packages/CompanionDeviceManager/res/layout/vendor_header.xml @@ -24,28 +24,32 @@ android:layout_gravity="center" android:paddingTop="24dp" android:paddingBottom="4dp" - android:paddingLeft="24dp" - android:paddingRight="24dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" android:visibility="gone" > <ImageView android:id="@+id/vendor_header_image" - android:layout_width="31dp" - android:layout_height="32dp" /> + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@string/vendor_icon_description" /> <TextView android:id="@+id/vendor_header_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="20dp" - android:layout_marginTop="5dp" - android:layout_toRightOf="@+id/header_image" /> + android:layout_marginStart="12dp" + android:layout_marginTop="12dp" + android:textSize="16sp" + android:layout_toEndOf="@+id/vendor_header_image" + android:textColor="?android:attr/textColorSecondary"/> <ImageButton android:id="@+id/vendor_header_button" android:background="@drawable/ic_info" - android:layout_width="31dp" - android:layout_height="32dp" - android:layout_alignParentRight="true" /> + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@string/vendor_header_button_description" + android:layout_alignParentEnd="true" /> </RelativeLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 586a0223b460..3d6bf152f93f 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -116,4 +116,10 @@ apps installed on your watch during setup will use the same permissions as your phone.\n\n These permissions may include access to your watch\u2019s microphone and location.</string> + <!--Description for vendor icon [CHAR LIMIT=30]--> + <string name="vendor_icon_description">App Icon</string> + + <!--Description for information icon [CHAR LIMIT=30]--> + <string name="vendor_header_button_description">More Information Button</string> + </resources> diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index c38323f5f73c..428f2dc2eb35 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -59,9 +59,8 @@ <style name="VendorHelperBackButton" parent="@android:style/Widget.Material.Button.Borderless.Colored"> - <item name="android:layout_width">60dp</item> - <item name="android:layout_height">36dp</item> - <item name="android:layout_marginTop">20dp</item> + <item name="android:layout_width">70dp</item> + <item name="android:layout_height">48dp</item> <item name="android:textAllCaps">false</item> <item name="android:textColor">@android:color/system_neutral1_900</item> <item name="android:background">@drawable/helper_back_button</item> @@ -71,8 +70,6 @@ parent="@android:style/Widget.Material.Button.Borderless.Colored"> <item name="android:layout_width">300dp</item> <item name="android:layout_height">56dp</item> - <item name="android:layout_marginLeft">24dp</item> - <item name="android:layout_marginRight">24dp</item> <item name="android:layout_marginBottom">2dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> @@ -84,8 +81,6 @@ parent="@android:style/Widget.Material.Button.Borderless.Colored"> <item name="android:layout_width">300dp</item> <item name="android:layout_height">56dp</item> - <item name="android:layout_marginLeft">24dp</item> - <item name="android:layout_marginRight">24dp</item> <item name="android:layout_marginTop">2dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> @@ -115,4 +110,10 @@ <item name="android:indeterminate">true</item> <item name="android:layout_centerInParent">true</item> </style> + + <style name="ScrollViewStyle"> + <item name="android:scrollbars">none</item> + <item name="android:fillViewport">true</item> + <item name="android:clipChildren">false</item> + </style> </resources>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/themes.xml b/packages/CompanionDeviceManager/res/values/themes.xml index e3fc67c50d75..1ea3968a5984 100644 --- a/packages/CompanionDeviceManager/res/values/themes.xml +++ b/packages/CompanionDeviceManager/res/values/themes.xml @@ -18,8 +18,8 @@ <style name="ChooserActivity" parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar"> - <item name="*android:windowFixedHeightMajor">100%</item> - <item name="*android:windowFixedHeightMinor">100%</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@android:color/transparent</item> </style> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 5e918641e49f..9e9ec04fbd93 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -51,6 +51,7 @@ import android.companion.CompanionDeviceManager; import android.companion.IAssociationRequestCallback; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.net.MacAddress; import android.os.Bundle; @@ -411,17 +412,19 @@ public class CompanionDeviceActivity extends FragmentActivity implements final Drawable vendorIcon; final CharSequence vendorName; final Spanned title; + int nightModeFlags = getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK; mPermissionTypes = new ArrayList<>(); try { vendorIcon = getVendorHeaderIcon(this, packageName, userId); vendorName = getVendorHeaderName(this, packageName, userId); - mVendorHeaderImage.setImageDrawable(vendorIcon); if (hasVendorIcon(this, packageName, userId)) { - mVendorHeaderImage.setColorFilter(getResources().getColor( - android.R.color.system_accent1_600, /* Theme= */null)); + int color = nightModeFlags == Configuration.UI_MODE_NIGHT_YES + ? android.R.color.system_accent1_200 : android.R.color.system_accent1_600; + mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null)); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Package u" + userId + "/" + packageName + " not found."); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index f333b86d4d52..fc5ff085139c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -123,6 +123,7 @@ public class CompanionDeviceDiscoveryService extends Service { intent.setAction(ACTION_START_DISCOVERY); intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest); sStateLiveData.setValue(DiscoveryState.STARTING); + sScanResultsLiveData.setValue(Collections.emptyList()); context.startService(intent); } @@ -173,7 +174,6 @@ public class CompanionDeviceDiscoveryService extends Service { @Override public void onDestroy() { - sScanResultsLiveData.setValue(Collections.emptyList()); super.onDestroy(); if (DEBUG) Log.d(TAG, "onDestroy()"); } @@ -187,7 +187,6 @@ public class CompanionDeviceDiscoveryService extends Service { mStopAfterFirstMatch = request.isSingleDevice(); mDiscoveryStarted = true; sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS); - sScanResultsLiveData.setValue(Collections.emptyList()); final List<DeviceFilter<?>> allFilters = request.getDeviceFilters(); final List<BluetoothDeviceFilter> btFilters = diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp index 7cf5385bd841..af3e2102e430 100644 --- a/packages/CtsShim/build/Android.bp +++ b/packages/CtsShim/build/Android.bp @@ -47,6 +47,7 @@ android_app { uses_libs: ["android.test.runner"], apex_available: [ + "//apex_available:platform", "com.android.apex.cts.shim.v2_apk_in_apex_upgrades", ], } diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java index 5693c2f22d1e..281f7ba6a5ea 100644 --- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java +++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java @@ -27,17 +27,20 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import androidx.annotation.Nullable; + import java.util.List; + /** * Utility class for find out when to show WorkPolicyInfo */ public class WorkPolicyUtils { - Context mContext; - PackageManager mPackageManager; - UserManager mUserManager; - DevicePolicyManager mDevicePolicyManager; + private final Context mContext; + private final PackageManager mPackageManager; + private final UserManager mUserManager; + private final DevicePolicyManager mDevicePolicyManager; private static final int USER_NULL = -10000; @@ -81,7 +84,12 @@ public class WorkPolicyUtils { return false; } - private Intent getWorkPolicyInfoIntentDO() { + /** + * Returns the work policy info intent if the device owner component exists, + * and returns {@code null} otherwise + */ + @Nullable + public Intent getWorkPolicyInfoIntentDO() { final ComponentName ownerComponent = getDeviceOwnerComponent(); if (ownerComponent == null) { return null; @@ -99,43 +107,55 @@ public class WorkPolicyUtils { return null; } - private Intent getWorkPolicyInfoIntentPO() { + @Nullable + private ComponentName getManagedProfileOwnerComponent(int managedUserId) { + if (managedUserId == USER_NULL) { + return null; + } + Context managedProfileContext; try { - final int managedUserId = getManagedProfileUserId(); - if (managedUserId == USER_NULL) { - return null; - } - - Context managedProfileContext = + managedProfileContext = mContext.createPackageContextAsUser( mContext.getPackageName(), 0, UserHandle.of(managedUserId) ); + } catch (PackageManager.NameNotFoundException e) { + return null; + } - DevicePolicyManager managedProfileDevicePolicyManager = - (DevicePolicyManager) - managedProfileContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - ComponentName ownerComponent = managedProfileDevicePolicyManager.getProfileOwner(); - if (ownerComponent == null) { - return null; - } - - // Only search for the required action in the Profile Owner's package - final Intent intent = - new Intent(Settings.ACTION_SHOW_WORK_POLICY_INFO) - .setPackage(ownerComponent.getPackageName()); - final List<ResolveInfo> activities = - mPackageManager.queryIntentActivitiesAsUser( - intent, 0, UserHandle.of(managedUserId)); - if (activities.size() != 0) { - return intent; - } + DevicePolicyManager managedProfileDevicePolicyManager = + (DevicePolicyManager) + managedProfileContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + ComponentName ownerComponent = managedProfileDevicePolicyManager.getProfileOwner(); + return ownerComponent; + } - return null; - } catch (PackageManager.NameNotFoundException e) { + /** + * Returns the work policy info intent if the profile owner component exists, + * and returns {@code null} otherwise + */ + @Nullable + public Intent getWorkPolicyInfoIntentPO() { + final int managedUserId = getManagedProfileUserId(); + ComponentName ownerComponent = getManagedProfileOwnerComponent(managedUserId); + if (ownerComponent == null) { return null; } + + // Only search for the required action in the Profile Owner's package + final Intent intent = + new Intent(Settings.ACTION_SHOW_WORK_POLICY_INFO) + .setPackage(ownerComponent.getPackageName()); + final List<ResolveInfo> activities = + mPackageManager.queryIntentActivitiesAsUser( + intent, 0, UserHandle.of(managedUserId)); + if (activities.size() != 0) { + return intent; + } + + return null; } + @Nullable private ComponentName getDeviceOwnerComponent() { if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) { return null; @@ -143,7 +163,10 @@ public class WorkPolicyUtils { return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser(); } - private int getManagedProfileUserId() { + /** + * Returns the user id of the managed profile, and returns {@code USER_NULL} otherwise + */ + public int getManagedProfileUserId() { List<UserHandle> allProfiles = mUserManager.getAllProfiles(); for (UserHandle uh : allProfiles) { int id = uh.getIdentifier(); diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 378e1bb18f52..b5d53c571867 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"As jy <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitsaai of die uitvoer verander, sal jou huidige uitsending stop"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Saai <xliff:g id="SWITCHAPP">%1$s</xliff:g> uit"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Verander uitvoer"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Voorspellingteruggebaaranimasies"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Aktiveer stelselanimasies vir voorspellingteruggebaar."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Hierdie instelling aktiveer stelselanimasies vir voorspellinggebaaranimasie. Dit vereis dat enableOnBackInvokedCallback per program op waar gestel word in die manifeslêer."</string> </resources> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 6d833f97764a..6258a471263e 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>ን ካሰራጩ ወይም ውፅዓትን ከቀየሩ የአሁኑ ስርጭትዎ ይቆማል"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ያሰራጩ"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ውፅዓትን ይቀይሩ"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"የግምት ጀርባ እነማዎች"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"ለግምት ጀርባ የስርዓት እንማዎችን ያንቁ።"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ይህ ቅንብር የስርዓት እነማዎችን ለመገመት የምልክት እነማን ያነቃል። በዝርዝር ሰነድ ፋይሉ ውስጥ በእያንዳንዱ መተግበሪያ enableOnBackInvokedCallbackን ወደ እውነት ማቀናበር ያስፈልገዋል።"</string> </resources> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index a3c4ac59d314..e042ba44d4a6 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"إذا أجريت بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g> أو غيَّرت جهاز الإخراج، سيتوقَف البث الحالي."</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"تغيير جهاز الإخراج"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index e4a6118894cb..3b6cd3143f48 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"যদি আপুনি <xliff:g id="SWITCHAPP">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰে অথবা আউটপুট সলনি কৰে, তেন্তে, আপোনাৰ বৰ্তমানৰ সম্প্ৰচাৰ বন্ধ হৈ যাব"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্ৰচাৰ কৰক"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"আউটপুট সলনি কৰক"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"প্ৰেডিক্টিভ বেক এনিমেশ্বন"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"প্ৰেডিক্টিভ বেকৰ বাবে ছিষ্টেম এনিমেশ্বন সক্ষম কৰক।"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"এই ছেটিংটোৱে প্ৰেডিক্টিভ বেক এনিমেশ্বনৰ বাবে ছিষ্টেম এনিমেশ্বন সক্ষম কৰে। ইয়াৰ বাবে মেনিফেষ্ট ফাইলত প্ৰতি এপত enableOnBackInvokedCallback সত্য বুলি ছেট কৰাৰ প্ৰয়োজন।"</string> </resources> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index ca78c68c83df..90bdd24f161e 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlasanız və ya nəticəni dəyişsəniz, cari yayımınız dayandırılacaq"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlayın"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Nəticəni dəyişdirin"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Proqnozlaşdırılan geri animasiyalar"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Proqnozlaşdırıcı geri jest üçün sistem animasiyalarını aktiv edin."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Bu ayar proqnozlaşdırıcı jest animasiyası üçün sistem animasiyalarını aktiv edir. Bu, manifest faylında hər bir tətbiq üçün enableOnBackInvokedCallback-in doğru kimi ayarlanmasını tələb edir."</string> </resources> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 0da57e4e7746..940f78d9e4a9 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -594,7 +594,7 @@ <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"Resetuj sesiju gosta"</string> <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Želite li da resetujete sesiju gosta?"</string> - <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite li da uklonite gosta?"</string> + <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite da uklonite gosta?"</string> <string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetuj"</string> <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ukloni"</string> <string name="guest_resetting" msgid="7822120170191509566">"Sesija gosta se resetuje…"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ako emitujete aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promenite izlaz, aktuelno emitovanje će se zaustaviti"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitujte aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Promenite izlaz"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije za pokret povratka sa predviđanjem"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogućite animacije sistema za pokret povratka sa predviđanjem."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ovo podešavanje omogućava animacije sistema za pokret povratka sa predviđanjem. Zahteva podešavanje dozvole enableOnBackInvokedCallback po aplikaciji na true u fajlu manifesta."</string> </resources> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 2c681da6d84f..3a9db9ecd7ce 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Пры пераключэнні на праграму \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" ці змяненні вываду бягучая трансляцыя спыняецца"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Трансляцыя праграмы \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\""</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Змяненне вываду"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 5b6d982492f3..5585598bbe35 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако предавате <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените изхода, текущото ви предаване ще бъде прекратено"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Предаване на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Промяна на изхода"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index bd812859b538..424b42113a90 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"আপনি <xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করলে বা আউটপুট পরিবর্তন করলে, আপনার বর্তমান সম্প্রচার বন্ধ হয়ে যাবে"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করুন"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"আউটপুট পরিবর্তন করুন"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"ফিরে যাওয়ার পূর্বাভাস সংক্রান্ত অ্যানিমেশন"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"ফিরে যাওয়া সংক্রান্ত পূর্বাভাসের জন্য সিস্টেম অ্যানিমেশন চালু করুন।"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"জেসচারের পূর্বাভাস সংক্রান্ত অ্যানিমেশন দেখাতে এই সেটিং সিস্টেম অ্যানিমেশন চালু করে। এই সেটিংয়ে \'ম্যানিফেস্ট\' ফাইলে প্রত্যেক অ্যাপে enableOnBackInvokedCallback অ্যাট্রিবিউটকে \'ট্রু\' (true) হিসেবে সেট করতে হয়।"</string> </resources> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index 258120f35cc3..1ea91df32619 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, trenutno emitiranje će se zaustaviti"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitiraj aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Promijeni izlaz"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije za pokret povratka s predviđanjem"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogući animacije sustava za pokret povratka s predviđanjem."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ova postavka omogućuje animacije sustava za animaciju pokreta s predviđanjem. Zahtijeva postavljanje dopuštenja enableOnBackInvokedCallback po aplikaciji na True u datoteci manifesta."</string> </resources> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 91dcbc91ab88..cfd91ec5ae15 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si emets <xliff:g id="SWITCHAPP">%1$s</xliff:g> o canvies la sortida, l\'emissió actual s\'aturarà"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emet <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Canvia la sortida"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 121402ccc9f3..7cb54d4c93da 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Pokud budete vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g> nebo změníte výstup, aktuální vysílání se zastaví"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Změna výstupu"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 6f4846ee8755..52adc58c3771 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Hvis du udsender <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller skifter output, stopper din aktuelle udsendelse"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Udsend <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Skift output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Foreslåede animationer for Tilbage"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Aktivér systemanimationer for foreslåede animationer for Tilbage."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Denne indstilling aktiverer systemanimationer for de foreslåede animationer for bevægelsen Tilbage. Dette forudsætter konfiguration af enableOnBackInvokedCallback som sand for hver app i manifestfilen."</string> </resources> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 7d66c837d53b..bf9ce299818b 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Wenn du <xliff:g id="SWITCHAPP">%1$s</xliff:g> streamst oder die Ausgabe änderst, wird dein aktueller Stream beendet"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> streamen"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ausgabe ändern"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index ef0f800656f9..78ba6fab6336 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Εάν κάνετε μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g> ή αλλάξετε την έξοδο, η τρέχουσα μετάδοση θα σταματήσει"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Αλλαγή εξόδου"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 1dc7b4291581..5f345a0791e8 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index 20e9308a5d46..881917b21378 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 1dc7b4291581..5f345a0791e8 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 1dc7b4291581..5f345a0791e8 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 70227dce71e4..c0b8f6bc3326 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Change output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Predictive back animations"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Enable system animations for predictive back."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"This setting enables system animations for predictive gesture animation. It requires setting per-app enableOnBackInvokedCallback to true in the manifest file."</string> </resources> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index d96e7f07a161..d2ac04d5bca2 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si transmites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu transmisión actual se detendrá"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambia la salida"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index c9c1eb18bf3a..4abbbc15f2eb 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -570,7 +570,7 @@ <string name="user_add_profile_item_title" msgid="3111051717414643029">"Perfil restringido"</string> <string name="user_add_user_title" msgid="5457079143694924885">"¿Añadir nuevo usuario?"</string> <string name="user_add_user_message_long" msgid="1527434966294733380">"Puedes compartir este dispositivo si creas más usuarios. Cada uno tendrá su propio espacio y podrá personalizarlo con aplicaciones, un fondo de pantalla y mucho más. Los usuarios también pueden ajustar opciones del dispositivo, como la conexión Wi‑Fi, que afectan a todos los usuarios.\n\nCuando añadas un usuario, tendrá que configurar su espacio.\n\nCualquier usuario puede actualizar aplicaciones de todos los usuarios. Es posible que no se transfieran los servicios y opciones de accesibilidad al nuevo usuario."</string> - <string name="user_add_user_message_short" msgid="3295959985795716166">"Al añadir un usuario nuevo, debe configurar su espacio.\n\nCualquier usuario puede actualizar las aplicaciones del resto de usuarios."</string> + <string name="user_add_user_message_short" msgid="3295959985795716166">"Al añadir un nuevo usuario, dicha persona debe configurar su espacio.\n\nCualquier usuario puede actualizar las aplicaciones del resto de usuarios."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"¿Configurar usuario ahora?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Asegúrate de que la persona está disponible en este momento para usar el dispositivo y configurar su espacio."</string> <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"¿Quieres configurar un perfil ahora?"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si emites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu emisión actual se detendrá"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambiar salida"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 37a6db28ee0b..c7ba159ae521 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Kui kannate rakendust <xliff:g id="SWITCHAPP">%1$s</xliff:g> üle või muudate väljundit, peatatakse teie praegune ülekanne"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Rakenduse <xliff:g id="SWITCHAPP">%1$s</xliff:g> ülekandmine"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Väljundi muutmine"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 689cca13afad..1a200532ec67 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -446,7 +446,7 @@ <string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Ikusmen-monokromia"</string> <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Daltonismoa (gorri-berdeak)"</string> <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string> - <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string> + <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (urdin-horia)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string> <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Baliteke koloreen zuzenketa lagungarria izatea hauek egin nahi dituzunean:<br/> <ol> <li>&nbsp;Koloreak zehaztasun handiagoz ikusi.</li> <li>&nbsp;Koloreak kendu, arreta gal ez dezazun.</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string> @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"Erabiltzailea"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Profil murriztua"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Beste erabiltzaile bat gehitu nahi duzu?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"Gailu hau beste pertsona batzuekin partekatzeko, sortu erabiltzaile gehiago. Erabiltzaile bakoitzak bere eremua izango du eta, bertan, aplikazioak, horma-papera eta antzekoak pertsonalizatu ahal izango ditu. Horrez gain, erabiltzaile guztiei eragin diezaieketen ezarpen batzuk ere doi daitezke; adibidez, wifi-konexioarena.\n\nErabiltzaile bat gehitzen duzunean, pertsona horrek berak konfiguratu beharko du bere eremua.\n\nEdozein erabiltzailek egunera ditzake beste erabiltzaile guztien aplikazioak. Baliteke erabilerraztasun-ezarpenak eta -zerbitzuak ez transferitzea erabiltzaile berriei."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"Gailu hau beste pertsona batzuekin partekatzeko, sortu erabiltzaile gehiago. Erabiltzaile bakoitzak bere eremua izango du eta, bertan, aplikazioak, horma-papera eta antzekoak pertsonalizatu ahal izango ditu. Horrez gain, agian erabiltzaile guztiei eragingo dieten ezarpen batzuk ere doi daitezke; adibidez, wifi-konexioarena.\n\nErabiltzaile bat gehitzen duzunean, pertsona horrek berak konfiguratu beharko du bere eremua.\n\nEdozein erabiltzailek egunera ditzake beste erabiltzaile guztien aplikazioak. Baliteke erabilerraztasun-ezarpenak eta -zerbitzuak ez transferitzea erabiltzaile berriei."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"Erabiltzaile bat gehitzen duzunean, erabiltzaile horrek bere eremua konfiguratu beharko du.\n\nEdozein erabiltzailek egunera ditzake beste erabiltzaile guztien aplikazioak."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Erabiltzailea konfiguratu?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Ziurtatu pertsona horrek gailua hartu eta bere eremua konfigura dezakeela"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa igortzen baduzu, edo audio-irteera aldatzen baduzu, une hartako igorpena eten egingo da"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Igorri <xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Aldatu audio-irteera"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index f1419102cecb..030d34ce4990 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"اگر <xliff:g id="SWITCHAPP">%1$s</xliff:g> را همهفرستی کنید یا خروجی را تغییر دهید، همهفرستی کنونی متوقف خواهد شد"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"همهفرستی <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"تغییر خروجی"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 32c52412359b..a90cb71d4779 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jos lähetät <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta tai muutat ulostuloa, nykyinen lähetyksesi loppuu"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Lähetä <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Muuta ulostuloa"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 51ddb5a1e153..cb32e0f6b1be 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou changez la sortie, votre diffusion actuelle s\'arrêtera"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Diffuser <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Changer la sortie"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 9cdcc1c42223..4367fe312bd2 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou que vous modifiez le résultat, votre annonce actuelle s\'arrêtera"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Diffuser <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Modifier le résultat"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index f5ea9d473021..ac8c6f37e509 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -604,7 +604,7 @@ <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"Realizaches demasiados intentos incorrectos. Eliminaranse os datos deste dispositivo."</string> <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"Realizaches demasiados intentos incorrectos. Eliminarase este usuario."</string> <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Realizaches demasiados intentos incorrectos. Eliminaranse este perfil de traballo e os datos asociados."</string> - <string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Ignorar"</string> + <string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Pechar"</string> <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Funcionamento predeterminado"</string> <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivado"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se emites contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou cambias de saída, a emisión en curso deterase"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitir contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambiar de saída"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 393d6ff8dbbd..ecef2ebb7396 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"જો તમે <xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો અથવા આઉટપુટ બદલો, તો તમારું હાલનું બ્રોડકાસ્ટ બંધ થઈ જશે"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"આઉટપુટ બદલો"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"પાછળના પૂર્વાનુમાનિત ઍનિમેશન્સ"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"પાછળના પૂર્વાનુમાનિત સંકેત માટે સિસ્ટમ ઍનિમેશન ચાલુ કરો."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"આ સેટિંગ પૂર્વાનુમાનિત સંકેત ઍનિમેશન માટે સિસ્ટમ ઍનિમેશન ચાલુ કરે છે. તેના માટે દરેક ઍપ માટે મેનિફેસ્ટ ફાઇલમાં enableOnBackInvokedCallbackને true પર સેટ કરવાની જરૂર પડે છે."</string> </resources> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 3ab013691d69..2177b664a4b3 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट शुरू करने पर या आउटपुट बदलने पर, आपका मौजूदा ब्रॉडकास्ट बंद हो जाएगा"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट करें"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"आउटपुट बदलें"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"प्रिडिक्टिव बैक ऐनिमेशन"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"प्रिडिक्टिव बैक के लिए सिस्टम ऐनिमेशन चालू करें."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"यह सेटिंग, सिस्टम के ऐनिमेशन को प्रिडिक्टिव जेस्चर ऐनिमेशन के लिए चालू कर देती है. मेनिफ़ेस्ट फ़ाइल में enableOnBackInvokedCallback की सेटिंग को हर ऐप्लिकेशन के हिसाब से \'सही\' पर सेट होना चाहिए."</string> </resources> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 82a7443a7050..fb72a91b44c7 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -583,7 +583,7 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"Profilni podaci"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"Prije izrade ograničenog profila trebate postaviti zaključavanje zaslona radi zaštite svojih aplikacija i osobnih podataka."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Postavi zaključavanje"</string> - <string name="user_switch_to_user" msgid="6975428297154968543">"Prelazak na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="user_switch_to_user" msgid="6975428297154968543">"Prijeđi na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string> <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Izrada novog gosta…"</string> <string name="add_user_failed" msgid="4809887794313944872">"Izrada novog korisnika nije uspjela"</string> @@ -591,7 +591,7 @@ <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="user_add_user" msgid="7876449291500212468">"Dodavanje korisnika"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"Poništi gostujuću sesiju"</string> <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Poništiti gostujuću sesiju?"</string> <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite li ukloniti gosta?"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, vaše će se trenutačno emitiranje zaustaviti"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Emitiranje aplikacije <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Promjena izlaza"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije za pokret povratka s predviđanjem"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogući animacije sustava za pokret povratka s predviđanjem."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ova postavka omogućuje animacije sustava za animaciju pokreta s predviđanjem. Zahtijeva postavljanje dopuštenja enableOnBackInvokedCallback po aplikaciji na True u datoteci manifesta."</string> </resources> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 862078055372..c74a5c3f4221 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"A(z) <xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése vagy a kimenet módosítása esetén a jelenlegi közvetítés leáll"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Kimenet módosítása"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index e7c0a3be33c8..9cf22e9f2116 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -593,7 +593,7 @@ <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string> <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"Վերակայել հյուրի աշխատաշրջանը"</string> - <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Վերակայե՞լ հյուրի աշխատաշրջանը"</string> + <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Զրոյացնե՞լ հյուրի աշխատաշրջանը"</string> <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Հեռացնե՞լ հյուրին"</string> <string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Զրոյացնել"</string> <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Հեռացնել"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Եթե հեռարձակեք <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը կամ փոխեք աուդիո ելքը, ձեր ընթացիկ հեռարձակումը կկանգնեցվի։"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Հեռարձակել <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Փոխել աուդիո ելքը"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 1a1268c4469f..4ba8287549f5 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jika Anda menyiarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau mengubah output, siaran saat ini akan dihentikan"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ubah output"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index e0ca6e35c985..0733dc424996 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ef þú sendir út <xliff:g id="SWITCHAPP">%1$s</xliff:g> eða skiptir um úttak lýkur yfirstandandi útsendingu"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Senda út <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Skipta um úttak"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index f04a39493ff2..25908b70df70 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambi l\'uscita, la trasmissione attuale viene interrotta"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Cambia uscita"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index c50b18079f52..58ea4270aa9a 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"אם משדרים את התוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g> או משנים את הפלט, השידור הנוכחי יפסיק לפעול"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"שידור תוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"שינוי הפלט"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 4464c8752f7b..a17b4a7187f8 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -569,8 +569,8 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"ユーザー"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"制限付きプロファイル"</string> <string name="user_add_user_title" msgid="5457079143694924885">"新しいユーザーを追加しますか?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"追加ユーザーを作成して、このデバイスを他のユーザーと共有できます。各ユーザーは各自のスペースを所有して、アプリや壁紙などのカスタマイズを行うことができます。Wi-Fi など、すべてのユーザーに影響するデバイス設定を変更することもできます。\n\n新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーは他のユーザーに代わってアプリを更新できます。ユーザー補助機能の設定とサービスは新しいユーザーに適用されないことがあります。"</string> - <string name="user_add_user_message_short" msgid="3295959985795716166">"新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーは他のユーザーに代わってアプリを更新できます。"</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"追加ユーザーを作成して、このデバイスを他のユーザーと共有できます。各ユーザーは各自のスペースを所有して、アプリや壁紙などのカスタマイズを行うことができます。Wi-Fi など、すべてのユーザーに影響するデバイス設定を変更することもできます。\n\n新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーがアプリを更新でき、その影響は他のユーザーにも及びます。ユーザー補助機能の設定とサービスは新しいユーザーに適用されないことがあります。"</string> + <string name="user_add_user_message_short" msgid="3295959985795716166">"新しく追加したユーザーは各自でスペースをセットアップする必要があります。\n\nすべてのユーザーがアプリを更新でき、その影響は他のユーザーにも及びます。"</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"ユーザーを今すぐセットアップ"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"ユーザーがデバイスを使って各自のスペースをセットアップできるようにします"</string> <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"プロファイルを今すぐセットアップしますか?"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャストしたり、出力を変更したりすると、現在のブロードキャストが停止します。"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャスト"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"出力を変更"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index c6a50ac9e565..1b822590bceb 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაციის შემთხვევაში ან აუდიოს გამოსასვლელის შეცვლისას, მიმდინარე ტრანსლაცია შეჩერდება"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაცია"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"აუდიოს გამოსასვლელის შეცვლა"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"უკან დაბრუნების ანიმაციის პროგნოზირება"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"უკან დაბრუნების პროგნოზირებადი ანიმაციისთვის სისტემის ანიმაციების ჩართვა."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ეს პარამეტრი ჩართავს სისტემის ანიმაციებს ჟესტების პროგნოზირებადი ანიმაციებისთვის. საჭიროა, რომ აღწერის ფაილში აპის enableOnBackInvokedCallback პარამეტრი იყოს ჭეშმარიტი."</string> </resources> diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml index a831df14bc7d..33fd25bbdaf6 100644 --- a/packages/SettingsLib/res/values-kk/arrays.xml +++ b/packages/SettingsLib/res/values-kk/arrays.xml @@ -54,9 +54,9 @@ <item msgid="9048424957228926377">"Әрқашан тексеру"</item> </string-array> <string-array name="hdcp_checking_summaries"> - <item msgid="4045840870658484038">"Ешқашан HDCP (жоғары кең жолақты сандық мазмұн қорғаушы) тексерулерін қолданбаңыз"</item> + <item msgid="4045840870658484038">"Ешқашан HDCP (жоғары кең жолақты цифрлық мазмұн қорғаушы) тексерулерін қолданбаңыз"</item> <item msgid="8254225038262324761">"HDCP тексерісін DRM мазмұны үшін ғана қолдану"</item> - <item msgid="6421717003037072581">"Әрқашан HDCP (жоғары кең жолақты сандық мазмұн қорғаушы) тексерулерін қолданыңыз"</item> + <item msgid="6421717003037072581">"Әрқашан HDCP (жоғары кең жолақты цифрлық мазмұн қорғаушы) тексерулерін қолданыңыз"</item> </string-array> <string-array name="bt_hci_snoop_log_entries"> <item msgid="695678520785580527">"Өшірулі"</item> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index e42dcbdee4d0..7af4bc33d428 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -344,7 +344,7 @@ <string name="enable_terminal_title" msgid="3834790541986303654">"Жергілікті терминал"</string> <string name="enable_terminal_summary" msgid="2481074834856064500">"Жергілікті шелл-код қол жетімділігін ұсынатын терминалды қолданбаны қосу"</string> <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP тексерісі"</string> - <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP (кең жолақты сандық мазмұн қорғау) тексеру мүмкіндігін орнату"</string> + <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP (кең жолақты цифрлық мазмұн қорғау) тексеру мүмкіндігін орнату"</string> <string name="debug_debugging_category" msgid="535341063709248842">"Түзету"</string> <string name="debug_app" msgid="8903350241392391766">"Түзету қолданбасын таңдау"</string> <string name="debug_app_not_set" msgid="1934083001283807188">"Түзету қолданбалары орнатылмаған."</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын тарату"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Аудио шығысын өзгерту"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 080fc79a653b..dc154ecfb718 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ប្រសិនបើអ្នកផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ឬប្ដូរឧបករណ៍បញ្ចេញសំឡេង ការផ្សាយបច្ចុប្បន្នរបស់អ្នកនឹងបញ្ឈប់"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"ការផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ប្ដូរឧបករណ៍បញ្ចេញសំឡេង"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index f6249a9fd0e1..af063c6a9813 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ನೀವು <xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿದರೆ ಅಥವಾ ಔಟ್ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿದರೆ, ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಪ್ರಸಾರವು ಸ್ಥಗಿತಗೊಳ್ಳುತ್ತದೆ"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿ"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ಔಟ್ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿ"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"ಮುನ್ನೋಟದ ಬ್ಯಾಕ್ ಆ್ಯನಿಮೇಶನ್ಗಳು"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"ಮುನ್ನೋಟದ ಬ್ಯಾಕ್ ಗೆಸ್ಚರ್ಗಾಗಿ ಸಿಸ್ಟಂ ಆ್ಯನಿಮೇಶನ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ಮುನ್ನೋಟದ ಗೆಸ್ಚರ್ ಆ್ಯನಿಮೇಶನ್ಗಾಗಿ ಸಿಸ್ಟಂ ಆ್ಯನಿಮೇಶನ್ಗಳನ್ನು ಈ ಸೆಟ್ಟಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸುತ್ತವೆ. ಇದನ್ನು ಮಾಡಲು, ಪ್ರತಿ ಆ್ಯಪ್ನ ಮ್ಯಾನಿಫೆಸ್ಟ್ ಫೈಲ್ನಲ್ಲಿರುವ enableOnBackInvokedCallback ಪ್ಯಾರಾಮೀಟರ್ ಅನ್ನು ಸರಿ ಎಂದು ಹೊಂದಿಸಬೇಕು."</string> </resources> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 53b4017345b9..d22d048f98b7 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 앱을 방송하거나 출력을 변경하면 기존 방송이 중단됩니다"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 방송"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"출력 변경"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 73830c51d3f2..a1f238ffecc0 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"Колдонуучу"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Чектелген профайл"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Жаңы колдонуучу кошосузбу?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"Эгер түзмөгүңүздү дагы бир адам колдонуп жаткан болсо, кошумча профилдерди түзүп коюңуз. Профилдин ээси аны өзү каалагандай жөндөп, тушкагаздарды коюп, керектүү колдонмолорду орнотуп алат. Мындан тышкары, колдонуучулар түзмөктүн Wi‑Fi´ды өчүрүү/күйгүзүү сыяктуу жалпы жөндөөлөрүн өзгөртө алат.\n\nПрофиль түзүлгөндөн кийин, аны жөндөп алуу керек.\n\nЖалпы колдонмолорду баары жаңырта алат, бирок атайын мүмкүнчүлүктөр өз-өзүнчө жөндөлөт."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"Эгер түзмөгүңүздү дагы бир адам колдонуп жаткан болсо, кошумча профилдерди түзүп коюңуз. Профилдин ээси аны өзү каалагандай тууралап, тушкагаздарды коюп, керектүү колдонмолорду орнотуп алат. Мындан тышкары, колдонуучулар түзмөктүн Wi‑Fi´ды өчүрүү/күйгүзүү сыяктуу жалпы параметрлерин өзгөртө алышат.\n\nПрофиль түзүлгөндөн кийин, аны тууралап алуу керек.\n\nЖалпы колдонмолорду баары жаңырта алат, бирок атайын мүмкүнчүлүктөр өз-өзүнчө жөндөлөт."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"Жаңы колдонуучу кошулганда, ал өз мейкиндигин түзүп алышы керек.\n\nКолдонмолорду бир колдонуучу жаңыртканда, ал калган бардык колдонуучулар үчүн да жаңырат."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Профилди жөндөйсүзбү?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Өз мейкиндигин жөндөп алышы үчүн, түзмөктү колдонуучуга беришиңиз керек."</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Эгер <xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарласаңыз же аудионун чыгуусун өзгөртсөңүз, учурдагы кабарлоо токтотулат"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарлоо"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Аудионун чыгуусун өзгөртүү"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 46ce28dab0c0..f50f053c8680 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ຫາກທ່ານອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ຫຼື ປ່ຽນເອົ້າພຸດ, ການອອກອາກາດປັດຈຸບັນຂອງທ່ານຈະຢຸດ"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"ອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ປ່ຽນເອົ້າພຸດ"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"ອະນິເມຊັນກັບຫຼັງແບບຄາດເດົາ"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"ເປີດການນຳໃຊ້ອະນິເມຊັນລະບົບສຳລັບການກັບຫຼັງແບບຄາດເດົາ."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ການຕັ້ງຄ່ານີ້ຈະເປີດການນຳໃຊ້ອະນິເມຊັນລະບົບສຳລັບອະນິເມຊັນທ່າທາງແບບຄາດເດົາ. ມັນຕ້ອງໃຊ້ການຕັ້ງຄ່າຕໍ່ແອັບ enableOnBackInvokedCallback ເປັນ true ໃນໄຟລ໌ manifest."</string> </resources> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 250d24af0d38..7229c0e58f46 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jei transliuosite „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“ arba pakeisite išvestį, dabartinė transliacija bus sustabdyta"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transliuoti „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Keisti išvestį"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index f72ba8e996fe..13020ac2241f 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ja sāksiet lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraidīšanu vai mainīsiet izvadi, pašreizējā apraide tiks apturēta"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraide"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Izvades maiņa"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index ac9199970d70..1a539c49dfde 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако емитувате на <xliff:g id="SWITCHAPP">%1$s</xliff:g> или го промените излезот, тековното емитување ќе запре"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Емитување на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Променете излез"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Предвидливи анимации отпозади"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Овозможете предвидливи системски анимации отпозади."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Поставкава ги овозможува системските анимации за предвидливи движења. Поставката треба да се постави на „точно“ преку апликација enableOnBackInvokedCallback во датотеката за манифест."</string> </resources> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index ea0d9ee9bda4..697de2cfce1c 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"ഉപയോക്താവ്"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"നിയന്ത്രിത പ്രൊഫൈൽ"</string> <string name="user_add_user_title" msgid="5457079143694924885">"പുതിയ ഉപയോക്താവിനെ ചേർക്കണോ?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"കൂടുതൽ ഉപയോക്താക്കളെ സൃഷ്ടിച്ചുകൊണ്ട് ഈ ഉപകരണം മറ്റുള്ളവരുമായി നിങ്ങൾക്ക് പങ്കിടാം. ആപ്പുകളും വാൾപേപ്പറുകളും മറ്റും ഉപയോഗിച്ച് ഇഷ്ടാനുസൃതമാക്കാൻ ഓരോ ഉപയോക്താവിനും സാധിക്കും. വൈഫൈ പോലെ എല്ലാവരെയും ബാധിക്കുന്ന ഉപകരണ ക്രമീകരണവും ഉപയോക്താക്കൾക്ക് ക്രമീകരിക്കാം.\n\nനിങ്ങളൊരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിക്ക് സ്വന്തമായ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\n എല്ലാ ഉപയോക്താക്കൾക്കുമായി ആപ്പുകൾ അപ്ഡേറ്റ് ചെയ്യാൻ ഏതൊരു ഉപയോക്താവിനുമാവും. ഉപയോഗസഹായി ക്രമീകരണവും സേവനങ്ങളും പുതിയ ഉപയോക്താവിന് കൈമാറുകയില്ല."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"കൂടുതൽ ഉപയോക്താക്കളെ സൃഷ്ടിച്ചുകൊണ്ട് ഈ ഉപകരണം മറ്റുള്ളവരുമായി നിങ്ങൾക്ക് പങ്കിടാം. ആപ്പുകളും വാൾപേപ്പറുകളും മറ്റും ഉപയോഗിച്ച് ഇഷ്ടാനുസൃതമാക്കാൻ ഓരോ ഉപയോക്താവിനും സാധിക്കും. വൈഫൈ പോലെ എല്ലാവരെയും ബാധിക്കുന്ന ഉപകരണ ക്രമീകരണവും ഉപയോക്താക്കൾക്ക് അഡ്ജസ്റ്റ് ചെയ്യാം.\n\nനിങ്ങളൊരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തി സ്വന്തമായ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\n ഏതെങ്കിലും ഉപയോക്താവിന് എല്ലാ ഉപയോക്താക്കൾക്കുമായി ആപ്പുകൾ അപ്ഡേറ്റ് ചെയ്യാനാകും. ഉപയോഗസഹായി ക്രമീകരണവും സേവനങ്ങളും പുതിയ ഉപയോക്താവിന് കൈമാറുകയില്ല."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"നിങ്ങൾ ഒരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിയ്ക്ക് അവരുടെ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\nമറ്റ് എല്ലാ ഉപയോക്താക്കൾക്കുമായി ഏതൊരു ഉപയോക്താവിനും ആപ്പുകൾ അപ്ഡേറ്റ് ചെയ്യാനാവും."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"ഉപയോക്താവിനെ ഇപ്പോൾ സജ്ജീകരിക്കണോ?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"ഉപകരണം എടുത്ത് ഇടം സജ്ജീകരിക്കുന്നതിന് വ്യക്തി ലഭ്യമാണെന്ന് ഉറപ്പാക്കുക"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"നിങ്ങൾ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുകയോ ഔട്ട്പുട്ട് മാറ്റുകയോ ചെയ്താൽ നിങ്ങളുടെ നിലവിലുള്ള ബ്രോഡ്കാസ്റ്റ് അവസാനിക്കും"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുക"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ഔട്ട്പുട്ട് മാറ്റുക"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index cd41b81381d1..d85eb910da34 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"Хэрэглэгч"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Хязгаарлагдсан профайл"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Шинэ хэрэглэгч нэмэх үү?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"Та нэмэлт хэрэглэгч үүсгэх замаар бусад хүмүүстэй энэ төхөөрөмжийг хуваалцаж болно. Хэрэглэгч тус бүр апп, дэлгэцийн зураг болон бусад зүйлээ өөрчлөх боломжтой хувийн орон зайтай байдаг. Түүнчлэн хэрэглэгч нь бүх хэрэглэгчид нөлөөлөх боломжтой Wi-Fi зэрэг төхөөрөмжийн тохиргоог өөрчлөх боломжтой.\n\nХэрэв та шинэ хэрэглэгч нэмэх бол тухайн хүн хувийн орон зайгаа бүрдүүлэх ёстой.\n\nХэрэглэгч бүр бусад бүх хэрэглэгчийн өмнөөс апп шинэчилж болно. Хүртээмжийн тохиргоо болон үйлчилгээг шинэ хэрэглэгчид шилжүүлэх боломжгүй байж болзошгүй."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"Та нэмэлт хэрэглэгч үүсгэх замаар бусад хүмүүстэй энэ төхөөрөмжийг хуваалцаж болно. Хэрэглэгч тус бүр апп, дэлгэцийн зураг болон бусад зүйлээ өөрчлөх боломжтой хувийн орон зайтай байна. Түүнчлэн хэрэглэгч нь бүх хэрэглэгчид нөлөөлөх боломжтой Wi-Fi зэрэг төхөөрөмжийн тохиргоог өөрчлөх боломжтой.\n\nХэрэв та шинэ хэрэглэгч нэмэх бол тухайн хүн хувийн орон зайгаа бүрдүүлэх ёстой.\n\nХэрэглэгч бүр бусад бүх хэрэглэгчийн өмнөөс апп шинэчилж болно. Хандалтын тохиргоо болон үйлчилгээг шинэ хэрэглэгчид шилжүүлэх боломжгүй байж болзошгүй."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"Та шинэ хэрэглэгч нэмбэл тухайн хүн өөрийн профайлыг тохируулах шаардлагатай.\n\nАль ч хэрэглэгч бүх хэрэглэгчийн апп-уудыг шинэчлэх боломжтой."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Хэрэглэгчийг одоо тохируулах уу?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Хэрэглэгч төхөөрөмжийг авч өөрийн профайлыг тохируулах боломжтой эсэхийг шалгана уу"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Хэрэв та <xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлсэн эсвэл гаралтыг өөрчилсөн бол таны одоогийн нэвтрүүлэлтийг зогсооно"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлэх"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Гаралтыг өөрчлөх"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Таамаглах боломжтой буцаах анимаци"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Таамаглах боломжтой буцаах зангаанд системийн анимацийг идэвхжүүлнэ үү."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Энэ тохиргоо нь таамаглах боломжтой зангааны анимацид системийн анимацийг идэвхжүүлнэ. Үүнд апп бүрд тодорхойлогч файлл enableOnBackInvokedCallback-г үнэн болгож тохируулахыг шаардана."</string> </resources> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 5c3578cea23d..0f07d30589a4 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"तुम्ही <xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण केल्यास किंवा आउटपुट बदलल्यास, तुमचे सध्याचे प्रसारण बंद होईल"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण करा"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"आउटपूट बदला"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"पूर्वानुमानित मागे जाण्याचे अॅनिमेशन"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"पूर्वानुमानित मागे जाण्यासाठीचे सिस्टीम अॅनिमेशन सुरू करा."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"हे सेटिंग पूर्वानुमानित जेश्चर ॲनिमेशनसाठी सिस्टीम ॲनिमेशन सुरू करते. यासाठी मॅनिफेस्ट फाइलमध्ये प्रति ॲप enableOnBackInvokedCallback सत्य वर सेट करणे आवश्यक आहे."</string> </resources> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 8a2fc2b0f450..ecee65782979 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jika anda siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau tukarkan output, siaran semasa anda akan berhenti"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Tukar output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Animasi kembali ramalan"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Dayakan animasi sistem untuk kembali ramalan."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Tetapan ini mendayakan animasi sistem untuk animasi gerak isyarat ramalan. Animasi sistem memerlukan tetapan enableOnBackInvokedCallback untuk setiap apl kepada benar dalam fail manifes."</string> </resources> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index c0aafc490564..b241e8d8e29c 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -660,4 +660,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ကို ထုတ်လွှင့်သောအခါ (သို့) အထွက်ကို ပြောင်းသောအခါ သင့်လက်ရှိထုတ်လွှင့်ခြင်း ရပ်သွားမည်"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ထုတ်လွှင့်ခြင်း"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"အထွက်ကို ပြောင်းခြင်း"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 51f5260ca966..df9e1df3a0a4 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Hvis du kringkaster <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller endrer utgangen, stopper den nåværende kringkastingen din"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Kringkast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Endre utgang"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 4e86795146dd..b476f656e610 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"तपाईंले <xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुभयो वा आउटपुट परिवर्तन गर्नुभयो भने तपाईंको हालको ब्रोडकास्ट रोकिने छ"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुहोस्"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"आउटपुट परिवर्तन गर्नुहोस्"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"पूर्वानुमानयुक्त ब्याक एनिमेसनहरू"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"पूर्वानुमानयुक्त ब्याक एनिमेसनका हकमा सिस्टम एनिमेसनहरू लागू गर्नुहोस्।"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"यो सेटिङले पूर्वानुमानयुक्त जेस्चर एनिमेसनका हकमा सिस्टम एनिमेनसहरू लागू गर्छ। म्यानिफेस्ट फाइलमा हरेक एपका हकमा enableOnBackInvokedCallback सेट गरी TRUE बनाएपछि मात्र यो सेटिङ अन गर्न मिल्छ।"</string> </resources> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index c9cbbebe4cba..2b7c1175ca15 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Als je <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzendt of de uitvoer wijzigt, wordt je huidige uitzending gestopt"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzenden"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Uitvoer wijzigen"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Voorspellende animaties voor gebaren voor terug"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Systeemanimaties aanzetten voor voorspellende animaties voor gebaren voor terug."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Met deze instelling zet je systeemanimaties aan voor voorspellende gebaaranimaties. Je moet enableOnBackInvokedCallback per app instellen op True in het manifestbestand."</string> </resources> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index eb58dd27db25..400bf2cffab9 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ଯଦି ଆପଣ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତି କିମ୍ବା ଆଉଟପୁଟ ବଦଳାନ୍ତି, ତେବେ ଆପଣଙ୍କ ବର୍ତ୍ତମାନର ବ୍ରଡକାଷ୍ଟ ବନ୍ଦ ହୋଇଯିବ"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତୁ"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ଆଉଟପୁଟ ବଦଳାନ୍ତୁ"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 644997192cdc..0f71f97e1dfb 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"ਵਰਤੋਂਕਾਰ"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"ਪ੍ਰਤਿਬੰਧਿਤ ਪ੍ਰੋਫਾਈਲ"</string> <string name="user_add_user_title" msgid="5457079143694924885">"ਕੀ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਨਾ ਹੈ?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"ਤੁਸੀਂ ਵਾਧੂ ਵਰਤੋਂਕਾਰ ਬਣਾ ਕੇ ਹੋਰਾਂ ਲੋਕਾਂ ਨਾਲ ਇਹ ਡੀਵਾਈਸ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹੋ। ਹਰੇਕ ਵਰਤੋਂਕਾਰ ਦੀ ਆਪਣੀ ਖੁਦ ਦੀ ਜਗ੍ਹਾ ਹੁੰਦੀ ਹੈ, ਜਿਸਨੂੰ ਉਹ ਐਪਾਂ ਅਤੇ ਵਾਲਪੇਪਰ ਆਦਿ ਨਾਲ ਵਿਉਂਤਬੱਧ ਕਰ ਸਕਦੇ ਹਨ। ਵਰਤੋਂਕਾਰ ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਵੀ ਵਿਵਸਥਿਤ ਕਰ ਸਕਦੇ ਹਨ ਜਿਵੇਂ ਵਾਈ‑ਫਾਈ ਜੋ ਹਰੇਕ \'ਤੇ ਅਸਰ ਪਾਉਂਦੀ ਹੈ।\n\nਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਉਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਜਗ੍ਹਾ ਸੈੱਟ ਅੱਪ ਕਰਨੀ ਪੈਂਦੀ ਹੈ।\n\nਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਬਾਕੀ ਸਾਰੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰ ਸਕਦਾ ਹੈ। ਸ਼ਾਇਦ ਪਹੁੰਚਯੋਗਤਾ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਨੂੰ ਕਿਸੇ ਨਵੇਂ ਵਰਤੋਂਕਾਰ ਨੂੰ ਟ੍ਰਾਂਸਫਰ ਨਾ ਕੀਤਾ ਜਾ ਸਕੇ।"</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"ਤੁਸੀਂ ਵਾਧੂ ਵਰਤੋਂਕਾਰ ਬਣਾ ਕੇ ਹੋਰਾਂ ਲੋਕਾਂ ਨਾਲ ਇਹ ਡੀਵਾਈਸ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹੋ। ਹਰੇਕ ਵਰਤੋਂਕਾਰ ਦੀ ਆਪਣੀ ਖੁਦ ਦੀ ਜਗ੍ਹਾ ਹੁੰਦੀ ਹੈ, ਜਿਸਨੂੰ ਉਹ ਐਪਾਂ ਅਤੇ ਵਾਲਪੇਪਰ ਆਦਿ ਨਾਲ ਵਿਉਂਤਬੱਧ ਕਰ ਸਕਦੇ ਹਨ। ਵਰਤੋਂਕਾਰ ਵਾਈ-ਫਾਈ ਵਰਗੀਆਂ ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਨੂੰ ਵੀ ਵਿਵਸਥਿਤ ਕਰ ਸਕਦੇ ਹਨ, ਜਿਸ ਨਾਲ ਹਰੇਕ ਵਰਤੋਂਕਾਰ \'ਤੇ ਅਸਰ ਪੈਂਦਾ ਹੈ।\n\nਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਉਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਜਗ੍ਹਾ ਸੈੱਟ ਅੱਪ ਕਰਨੀ ਪੈਂਦੀ ਹੈ।\n\nਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਬਾਕੀ ਸਾਰੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰ ਸਕਦਾ ਹੈ। ਸ਼ਾਇਦ ਪਹੁੰਚਯੋਗਤਾ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਨੂੰ ਕਿਸੇ ਨਵੇਂ ਵਰਤੋਂਕਾਰ ਨੂੰ ਟ੍ਰਾਂਸਫਰ ਨਾ ਕੀਤਾ ਜਾ ਸਕੇ।"</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਉਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਜਗ੍ਹਾ ਸੈੱਟਅੱਪ ਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ।\n\nਕੋਈ ਵੀ ਵਰਤੋਂਕਾਰ ਹੋਰ ਸਾਰੇ ਵਰਤੋਂਕਾਰਾਂ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰ ਸਕਦਾ ਹੈ।"</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"ਕੀ ਹੁਣ ਵਰਤੋਂਕਾਰ ਸੈੱਟ ਅੱਪ ਕਰਨਾ ਹੈ?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਵਿਅਕਤੀ ਡੀਵਾਈਸ ਵਰਤਣ ਅਤੇ ਆਪਣੀ ਜਗ੍ਹਾ ਦੇ ਸੈੱਟ ਅੱਪ ਲਈ ਉਪਲਬਧ ਹੈ"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ਜੇ ਤੁਸੀਂ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰਦੇ ਹੋ ਜਾਂ ਆਊਟਪੁੱਟ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡਾ ਮੌਜੂਦਾ ਪ੍ਰਸਾਰਨ ਰੁਕ ਜਾਵੇਗਾ"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰੋ"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ਆਊਟਪੁੱਟ ਬਦਲੋ"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index b40308e792e9..85fa056bc37f 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Jeśli transmitujesz aplikację <xliff:g id="SWITCHAPP">%1$s</xliff:g> lub zmieniasz dane wyjściowe, Twoja obecna transmisja zostanie zakończona"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmisja aplikacji <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Zmień dane wyjściowe"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index a0355b9c419e..b773b5ccbd3c 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Mudar saída"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 5b4172536a1f..23b32785bd93 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"Utilizador"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Perfil restrito"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Adicionar novo utilizador?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"Pode partilhar este dispositivo com outras pessoas ao criar utilizadores adicionais. Cada utilizador possui o seu próprio espaço, que pode ser personalizado com aplicações, imagens de fundo, etc. Os utilizadores também podem ajustar as definições do dispositivo, como o Wi‑Fi, que afetam os restantes utilizadores.\n\nAo adicionar um novo utilizador, essa pessoa tem de configurar o respetivo espaço.\n\nQualquer utilizador pode atualizar aplicações para todos os outros utilizadores. Os serviços e as definições de acessibilidade podem não ser transferidos para o novo utilizador."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"Pode partilhar este dispositivo com outras pessoas ao criar utilizadores adicionais. Cada utilizador possui o seu próprio espaço, que pode ser personalizado com apps, imagens de fundo, etc. Os utilizadores também podem ajustar as definições do dispositivo, como o Wi‑Fi, que afetam os restantes utilizadores.\n\nAo adicionar um novo utilizador, essa pessoa tem de configurar o respetivo espaço.\n\nQualquer utilizador pode atualizar apps para todos os outros utilizadores. Os serviços e as definições de acessibilidade podem não ser transferidos para o novo utilizador."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"Ao adicionar um novo utilizador, essa pessoa tem de configurar o respetivo espaço.\n\nQualquer utilizador pode atualizar aplicações para todos os outros utilizadores."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurar o utilizador agora?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Certifique-se de que a pessoa está disponível para levar o dispositivo e configurar o seu espaço"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se transmitir a app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou alterar a saída, a sua transmissão atual é interrompida"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmita a app <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Altere a saída"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Animações de gestos para voltar preditivos"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Ative as animações do sistema para gestos para voltar preditivos."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Esta definição ativa animações do sistema para a animação de gestos preditivos. Requer a definição do atributo enableOnBackInvokedCallback por app como verdadeiro no ficheiro de manifesto."</string> </resources> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index a0355b9c419e..b773b5ccbd3c 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Mudar saída"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 5a773cf7f7ad..d9627a4578eb 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Dacă difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbați rezultatul, difuzarea actuală se va opri"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Schimbați rezultatul"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 3ce46e7bd0c1..3e3693df6288 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Если вы начнете транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" или смените целевое устройство, текущая трансляция прервется."</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\""</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Транслировать на другое устройство"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Анимации подсказки для жеста \"Назад\""</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Включить системную анимацию подсказки для жеста \"Назад\"."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"С помощью этого параметра можно включить системные анимации подсказок для жестов. Для этого нужно установить значение true для enableOnBackInvokedCallback в файле манифеста приложения."</string> </resources> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 4e01d6157a80..b3c8eff93d14 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"ඔබ <xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය කළහොත් හෝ ප්රතිදානය වෙනස් කළහොත්, ඔබගේ වත්මන් විකාශනය නවතිනු ඇත."</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"ප්රතිදානය වෙනස් කරන්න"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 143941de20cb..6374cb71ff23 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -280,7 +280,7 @@ <string name="wifi_display_certification" msgid="1805579519992520381">"Certifikácia bezdrôtového zobrazenia"</string> <string name="wifi_verbose_logging" msgid="1785910450009679371">"Podrobné denníky Wi‑Fi"</string> <string name="wifi_scan_throttling" msgid="2985624788509913617">"Pribrzdiť vyhľadávanie sietí Wi‑Fi"</string> - <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Randomizácia dočasnej adresy MAC siete Wi‑Fi"</string> + <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Randomizovať dočasnú adresu MAC siete Wi‑Fi"</string> <string name="mobile_data_always_on" msgid="8275958101875563572">"Mobilné dáta ponechať vždy aktívne"</string> <string name="tethering_hardware_offload" msgid="4116053719006939161">"Hardvérová akcelerácia tetheringu"</string> <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Zobrazovať zariadenia Bluetooth bez názvov"</string> @@ -381,7 +381,7 @@ <string name="debug_layout_summary" msgid="8825829038287321978">"Zobraziť vo výstrižku ohraničenie, okraje a pod."</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Rozloženie sprava doľava"</string> <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"Vynútiť pre všetky jazyky rozloženie obrazovky sprava doľava"</string> - <string name="window_blurs" msgid="6831008984828425106">"Povolenie rozmazania na úrovni okna"</string> + <string name="window_blurs" msgid="6831008984828425106">"Povoliť rozmazanie na úrovni okna"</string> <string name="force_msaa" msgid="4081288296137775550">"Vynútiť 4x MSAA"</string> <string name="force_msaa_summary" msgid="9070437493586769500">"Povoliť 4x MSAA v aplikáciách OpenGL ES 2.0"</string> <string name="show_non_rect_clip" msgid="7499758654867881817">"Ladiť operácie s neobdĺžnikovými výstrižkami"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ak vysielate aplikáciu <xliff:g id="SWITCHAPP">%1$s</xliff:g> alebo zmeníte výstup, aktuálne vysielanie bude zastavené"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Vysielanie aplikácie <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Zmena výstupu"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Prediktívne animácie gesta Späť"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Povoľte animácie v systéme pre prediktívne gesto Späť"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Toto nastavenie povoľuje animácie v systéme na účely prediktívnej animácie gest. Vyžaduje nastavenie povolenia enableOnBackInvokedCallback na pravdu v súbore manifestu konkrétnej aplikácie."</string> </resources> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 2ffa8bf097f4..60c51dce2bdd 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -568,7 +568,7 @@ <string name="user_add_profile_item_summary" msgid="5418602404308968028">"Dostop do aplikacij in vsebine iz vašega računa lahko omejite"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"Uporabnik"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Omejen profil"</string> - <string name="user_add_user_title" msgid="5457079143694924885">"Dodajanje novega uporabnika?"</string> + <string name="user_add_user_title" msgid="5457079143694924885">"Želite dodati uporabnika?"</string> <string name="user_add_user_message_long" msgid="1527434966294733380">"To napravo lahko delite z drugimi tako, da ustvarite dodatne uporabnike. Vsak ima svoj prostor, ki ga lahko prilagodi z aplikacijami, ozadji in drugim. Uporabniki lahko tudi prilagodijo nastavitve naprave, ki vplivajo na vse, na primer nastavitve omrežja Wi-Fi.\n\nKo dodate novega uporabnika, mora ta nastaviti svoj prostor.\n\nVsak uporabnik lahko posodobi aplikacije za vse druge uporabnike. Nastavitve in storitve funkcij za ljudi s posebnimi potrebami morda ne bodo prenesene v prostor novega uporabnika."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"Ko dodate novega uporabnika, mora ta nastaviti svoj prostor.\n\nVsak uporabnik lahko posodobi aplikacije za vse druge uporabnike."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Želite uporabnika nastaviti zdaj?"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Če oddajate aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g> ali spremenite izhod, bo trenutno oddajanje ustavljeno."</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Oddajaj aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Sprememba izhoda"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Animacije poteze za nazaj s predvidevanjem"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Omogoči sistemske animacije za potezo za nazaj s predvidevanjem."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ta nastavitev omogoča sistemske animacije za animacijo poteze s predvidevanjem. V datoteki manifesta mora biti parameter »enableOnBackInvokedCallback« za posamezno aplikacijo nastavljen na »true«."</string> </resources> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 208da7a7e133..e6ade6c53152 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Nëse transmeton <xliff:g id="SWITCHAPP">%1$s</xliff:g> ose ndryshon daljen, transmetimi yt aktual do të ndalojë"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Transmeto <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ndrysho daljen"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index eba79105f437..c06e3f60a276 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -594,7 +594,7 @@ <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"Ресетуј сесију госта"</string> <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Желите ли да ресетујете сесију госта?"</string> - <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Желите ли да уклоните госта?"</string> + <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Желите да уклоните госта?"</string> <string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Ресетуј"</string> <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Уклони"</string> <string name="guest_resetting" msgid="7822120170191509566">"Сесија госта се ресетује…"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако емитујете апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените излаз, актуелно емитовање ће се зауставити"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Емитујте апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Промените излаз"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Анимације за покрет повратка са предвиђањем"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Омогућите анимације система за покрет повратка са предвиђањем."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ово подешавање омогућава анимације система за покрет повратка са предвиђањем. Захтева подешавање дозволе enableOnBackInvokedCallback по апликацији на true у фајлу манифеста."</string> </resources> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 1c1c38fc193b..0ca0348107e3 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"Användare"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Begränsad profil"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Lägga till ny användare?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dela enheten med andra om du skapar flera användare. Alla användare får sitt eget utrymme som de kan anpassa som de vill med appar, bakgrund och så vidare. Användarna kan även ändra enhetsinställningar som påverkar alla, till exempel Wi‑Fi.\n\nNär du lägger till en ny användare måste han eller hon konfigurera sitt utrymme.\n\nAlla användare kan uppdatera appar för samtliga användares räkning. Tillgänglighetsinställningar och tjänster kanske inte överförs till den nya användaren."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dela enheten med andra om du skapar flera användare. Alla användare får sitt eget utrymme som de kan anpassa som de vill med appar, bakgrund och så vidare. Användarna kan även ändra enhetsinställningar som påverkar alla, till exempel wifi.\n\nNär du lägger till en ny användare måste han eller hon konfigurera sitt utrymme.\n\nAlla användare kan uppdatera appar för samtliga användares räkning. Tillgänglighetsinställningar och tjänster kanske inte överförs till den nya användaren."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"När du lägger till en ny användare måste den personen konfigurera sitt utrymme.\n\nAlla användare kan uppdatera appar för samtliga användares räkning."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Konfigurera användare nu?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Kontrollera att personen finns tillgänglig för att konfigurera sitt utrymme på enheten"</string> @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Om en utsändning från <xliff:g id="SWITCHAPP">%1$s</xliff:g> pågår eller om du byter ljudutgång avbryts den nuvarande utsändningen"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Sänd från <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Byt ljudutgång"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 3280e4fe1462..9775c2579a3b 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ikiwa unatangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g> au unabadilisha maudhui, tangazo lako la sasa litasimamishwa"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Tangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Badilisha maudhui"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 236b74bfcb57..a041cb831146 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"நீங்கள் <xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பினாலோ அவுட்புட்டை மாற்றினாலோ உங்களின் தற்போதைய ஒலிபரப்பு நிறுத்தப்படும்"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பு"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"அவுட்புட்டை மாற்று"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"கணிக்கக்கூடிய பின்செல் சைகைக்கான அனிமேஷன்கள்"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"கணிக்கக்கூடிய பின்செல் சைகைக்காகச் சிஸ்டம் அனிமேஷன்களை இயக்கும்."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"கணிக்கக்கூடிய சைகைக்கான அனிமேஷனுக்காக இந்த அமைப்பு சிஸ்டம் அனிமேஷன்களை இயக்கும். மெனிஃபெஸ்ட் ஃபைலில் ஒவ்வொரு ஆப்ஸுக்கும் enableOnBackInvokedCallbackகை \'சரி\' என அமைக்க வேண்டும்."</string> </resources> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index c25966c2d429..83bbeb6f7546 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -583,7 +583,7 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ప్రొఫైల్ సమాచారం"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"మీరు పరిమితం చేయబడిన ప్రొఫైల్ను క్రియేట్ చేయడానికి ముందు, మీ యాప్లు మరియు వ్యక్తిగత డేటాను రక్షించడానికి స్క్రీన్ లాక్ను సెటప్ చేయాల్సి ఉంటుంది."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"లాక్ను సెట్ చేయి"</string> - <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు స్విచ్ చేయి"</string> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు స్విచ్ చేయి"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string> <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"కొత్త అతిథిని క్రియేట్ చేస్తోంది…"</string> <string name="add_user_failed" msgid="4809887794313944872">"కొత్త యూజర్ను క్రియేట్ చేయడం విఫలమైంది"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"మీరు <xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేస్తే లేదా అవుట్పుట్ను మార్చినట్లయితే, మీ ప్రస్తుత ప్రసారం ఆగిపోతుంది"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేయండి"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"అవుట్పుట్ను మార్చండి"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"ఊహించదగిన బ్యాక్ యానిమేషన్లు"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"ఊహించదగిన బ్యాక్ యానిమేషన్ల కోసం సిస్టమ్ యానిమేషన్లను ఎనేబుల్ చేయండి."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"ఊహించదగిన సంజ్ఞ యానిమేషన్ కోసం ఈ సెట్టింగ్ సిస్టమ్ యానిమేషన్లను ఎనేబుల్ చేస్తుంది. దీనికి మ్యానిఫెస్ట్ ఫైల్లో ఒక్కో యాప్లో enableOnBackInvokedCallback సెట్టింగ్ను ఒప్పునకు సెట్ చేయవలసి ఉంటుంది."</string> </resources> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index f31fde6048d8..848c8f1b9528 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -569,7 +569,7 @@ <string name="user_add_user_item_title" msgid="2394272381086965029">"ผู้ใช้"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"โปรไฟล์ที่ถูกจำกัด"</string> <string name="user_add_user_title" msgid="5457079143694924885">"ต้องการเพิ่มผู้ใช้ใหม่ใช่ไหม"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"คุณมีสิทธิ์แชร์อุปกรณ์นี้กับผู้อื่นได้โดยการเพิ่มผู้ใช้ ซึ่งแต่ละคนจะมีพื้นที่ของตนเองและปรับใช้กับแอป วอลเปเปอร์ และรายการอื่นๆ ได้ อีกทั้งยังปรับการตั้งค่าอุปกรณ์ได้ด้วย เช่น Wi‑Fi ซึ่งจะมีผลกับทุกคน\n\nเมื่อคุณเพิ่มผู้ใช้ใหม่ ผู้ใช้ดังกล่าวจะต้องตั้งค่าพื้นที่ของตน\n\nผู้ใช้ทุกคนมีสิทธิ์อัปเดตแอปให้กับผู้ใช้รายอื่น การตั้งค่าและบริการสำหรับการช่วยเหลือพิเศษอาจโอนไปยังผู้ใช้ใหม่ไม่ได้"</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"คุณมีสิทธิ์แชร์อุปกรณ์นี้กับผู้อื่นได้โดยการเพิ่มผู้ใช้ แต่ละคนจะมีพื้นที่ของตนเองซึ่งปรับใช้กับแอป วอลเปเปอร์ และรายการอื่นๆ ได้ อีกทั้งยังปรับการตั้งค่าอุปกรณ์ได้ด้วย เช่น Wi‑Fi ซึ่งจะมีผลกับทุกคน\n\nเมื่อคุณเพิ่มผู้ใช้ใหม่ ผู้ใช้ดังกล่าวจะต้องตั้งค่าพื้นที่ของตน\n\nผู้ใช้ทุกคนมีสิทธิ์อัปเดตแอปให้ผู้ใช้รายอื่น การตั้งค่าและบริการสำหรับการช่วยเหลือพิเศษอาจโอนไปยังผู้ใช้ใหม่ไม่ได้"</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"เมื่อคุณเพิ่มผู้ใช้ใหม่ ผู้ใช้ดังกล่าวจะต้องตั้งค่าพื้นที่ของตนเอง\n\nผู้ใช้ทุกคนสามารถอัปเดตแอปสำหรับผู้ใช้รายอื่นได้"</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"ตั้งค่าผู้ใช้เลยไหม"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"ตรวจสอบว่าบุคคลดังกล่าวสามารถนำอุปกรณ์ไปตั้งค่าพื้นที่ของตนได้"</string> @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"หากคุณออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g> หรือเปลี่ยนแปลงเอาต์พุต การออกอากาศในปัจจุบันจะหยุดลง"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"ออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"เปลี่ยนเอาต์พุต"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"การเคลื่อนไหวย้อนกลับแบบคาดเดา"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"เปิดใช้การเคลื่อนไหวของระบบสำหรับท่าทางสัมผัสย้อนกลับแบบคาดเดา"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"การตั้งค่านี้จะเปิดใช้การเคลื่อนไหวของระบบสำหรับการเคลื่อนไหวจากท่าทางสัมผัสแบบคาดเดา โดยต้องตั้งค่า enableOnBackInvokedCallback สำหรับแต่ละแอปให้เป็น \"จริง\" ในไฟล์ Manifest"</string> </resources> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 7d4a4b49bc0d..40f843517fe4 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Kung magbo-broadcast ka ng <xliff:g id="SWITCHAPP">%1$s</xliff:g> o babaguhin mo ang output, hihinto ang iyong kasalukuyang broadcast"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"I-broadcast ang <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Baguhin ang output"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Mga animation ng predictive na pagbalik"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"I-enable ang mga animation ng system para sa predictive na pagbalik."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Ine-enable ng setting na ito ang mga animation ng system para sa animation ng predictive na galaw. Kinakailangan nitong itakda sa true ang enableOnBackInvokedCallback sa bawat app sa manifest file."</string> </resources> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 15c9cac8237b..b81c1ac1c554 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapar veya çıkışı değiştirirseniz mevcut anonsunuz duraklatılır"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapın"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Çıkışı değiştirme"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 0cbbae82aabf..07f3d823cce1 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Змінити додаток для трансляції на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Змінити аудіовихід"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 3056ef7a0308..59b9fc915683 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"اگر آپ <xliff:g id="SWITCHAPP">%1$s</xliff:g> براڈکاسٹ کرتے ہیں یا آؤٹ پٹ کو تبدیل کرتے ہیں تو آپ کا موجودہ براڈکاسٹ رک جائے گا"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> پر براڈکاسٹ کریں"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"آؤٹ پٹ تبدیل کریں"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"پیچھے جانے کے اشارے کی پیش گوئی والی اینیمیشنز"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"پیچھے جانے کے پیش گوئی والے اشارے کے لیے سسٹم اینیمیشنز فعال کریں۔"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"یہ ترتیب پیش گوئی والی اشارے کی اینیمیشن کے لیے سسٹم کی اینیمیشنز کو فعال کرتی ہے۔ اس کے لیے manifest فائل میں فی ایپ enableOnBackInvokedCallback کو درست پر سیٹ کرنے کی ضرورت ہے۔"</string> </resources> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 348e7950a0e6..29f7f97fabab 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Agar <xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya qilsangiz yoki ovoz chiqishini oʻzgartirsangiz, joriy translatsiya toʻxtab qoladi"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Ovoz chiqishini oʻzgartirish"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Taxminiy qaytish animatsiyalari"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Taxminiy qaytish uchun tizim animatsiyalarini yoqish."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Bu sozlamalar taxminiy qaytish animatsiyalari uchun tizim animatsiyalarini faollashtiradi. Buning uchun har bir ilovaning manifest faylida enableOnBackInvokedCallback parametri “true” qiymatida boʻlishi lozim."</string> </resources> diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml index ea5230c8be10..4cf8ff49b636 100644 --- a/packages/SettingsLib/res/values-vi/arrays.xml +++ b/packages/SettingsLib/res/values-vi/arrays.xml @@ -178,13 +178,13 @@ <item msgid="2983219471251787208">"8 MB/vùng đệm nhật ký"</item> </string-array> <string-array name="select_logpersist_titles"> - <item msgid="704720725704372366">"Tắt"</item> + <item msgid="704720725704372366">"Đang tắt"</item> <item msgid="6014837961827347618">"Tất cả"</item> <item msgid="7387060437894578132">"Tất cả trừ đài"</item> <item msgid="7300881231043255746">"chỉ kernel"</item> </string-array> <string-array name="select_logpersist_summaries"> - <item msgid="97587758561106269">"Tắt"</item> + <item msgid="97587758561106269">"Đang tắt"</item> <item msgid="7126170197336963369">"Tất cả lần tải nhật ký"</item> <item msgid="7167543126036181392">"Tất cả trừ lần tải nhật ký qua đài"</item> <item msgid="5135340178556563979">"chỉ vùng đệm nhật ký kernel"</item> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 5c4932fb0eaa..98388bd5a310 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Nếu bạn phát <xliff:g id="SWITCHAPP">%1$s</xliff:g> hoặc thay đổi đầu ra, phiên truyền phát hiện tại sẽ dừng"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Phát <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Thay đổi đầu ra"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Ảnh động vuốt ngược dự đoán"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Cho phép sử dụng ảnh động hệ thống cho chức năng vuốt ngược dự đoán."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Cài đặt này cho phép sử dụng ảnh động hệ thống cho ảnh động cử chỉ dự đoán. Nó yêu cầu cài đặt cho mỗi ứng dụng chuyển enableOnBackInvokedCallback thành lệnh true trong tệp kê khai."</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 9bb725a119d9..121a24acc21e 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -653,10 +653,13 @@ <string name="allow_turn_screen_on" msgid="6194845766392742639">"允许开启屏幕"</string> <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用开启屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时开启屏幕。"</string> <string name="bt_le_audio_scan_qr_code" msgid="3521809854780392679">"扫描二维码"</string> - <string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"将扫描器对准下方二维码,即可开始收听"</string> + <string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"将取景框对准二维码,即可开始收听"</string> <string name="bt_le_audio_qr_code_is_not_valid_format" msgid="6092191081849434734">"二维码的格式无效"</string> <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string> <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"更改输出来源"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"预测性返回手势动画"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"启用系统动画作为预测性返回手势动画。"</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"此设置将启用系统动画作为预测性手势动画。这要求在清单文件中将单个应用的 enableOnBackInvokedCallback 设为 true。"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 8a581b7c4eb6..4f20f68e248f 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"變更輸出來源"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index f77e88905743..dc7f9e780841 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -659,4 +659,10 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"變更輸出來源"</string> + <!-- no translation found for back_navigation_animation (8105467568421689484) --> + <skip /> + <!-- no translation found for back_navigation_animation_summary (741292224121599456) --> + <skip /> + <!-- no translation found for back_navigation_animation_dialog (8696966520944625596) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 1cb1fa443e15..bc8c6feda1cf 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -659,4 +659,7 @@ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Uma usakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g> noma ushintsha okuphumayo, ukusakaza kwakho kwamanje kuzoma"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Sakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Shintsha okuphumayo"</string> + <string name="back_navigation_animation" msgid="8105467568421689484">"Ukubikezelwa kwasemuva kopopayi"</string> + <string name="back_navigation_animation_summary" msgid="741292224121599456">"Nika amandla ukubikezela emuva kopopayi besistimu."</string> + <string name="back_navigation_animation_dialog" msgid="8696966520944625596">"Leli sethingi livumela opopayi besistimu mayelana nokuthinta okubikezelwayo kopopayi. Idinga ukusetha i-app ngayinye ku-enableOnBackInvokedCallback ukuze iqinisekise ifayela le-manifest."</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index cc4fef8399c3..7f65837d5716 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -268,9 +268,9 @@ public class AppUtils { /** * Preload the top N icons of app entry list. * - * @param context caller's context + * @param context caller's context * @param appEntries AppEntry list of ApplicationsState - * @param number the number of Top N icons of the appEntries + * @param number the number of Top N icons of the appEntries */ public static void preloadTopIcons(Context context, ArrayList<ApplicationsState.AppEntry> appEntries, int number) { @@ -286,6 +286,19 @@ public class AppUtils { } } + /** + * Returns a boolean indicating whether this app is installed or not. + * + * @param appEntry AppEntry of ApplicationsState. + * @return true if the app is in installed state. + */ + public static boolean isAppInstalled(ApplicationsState.AppEntry appEntry) { + if (appEntry == null || appEntry.info == null) { + return false; + } + return (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0; + } + private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) { if (appEntry.mounted != mounted) { synchronized (appEntry) { diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java index 8a1e91b4dcd8..3e7481a61f89 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java @@ -147,6 +147,7 @@ public class AvatarPickerActivity extends Activity { mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false); mAdapter.mSelectedPosition = savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE); + mDoneButton.setEnabled(mAdapter.mSelectedPosition != AvatarAdapter.NONE); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java index 80ee86f5e489..3b542ccea635 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java @@ -54,6 +54,7 @@ public class EditUserInfoController { private Dialog mEditUserInfoDialog; private Bitmap mSavedPhoto; + private Drawable mSavedDrawable; private EditUserPhotoController mEditUserPhotoController; private boolean mWaitingForActivityResult = false; private final String mFileAuthority; @@ -68,6 +69,7 @@ public class EditUserInfoController { } mEditUserInfoDialog = null; mSavedPhoto = null; + mSavedDrawable = null; } /** @@ -170,7 +172,8 @@ public class EditUserInfoController { private Drawable getUserIcon(Activity activity, Drawable defaultUserIcon) { if (mSavedPhoto != null) { - return CircleFramedDrawable.getInstance(activity, mSavedPhoto); + mSavedDrawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto); + return mSavedDrawable; } return defaultUserIcon; } @@ -229,6 +232,6 @@ public class EditUserInfoController { EditUserPhotoController createEditUserPhotoController(Activity activity, ActivityStarter activityStarter, ImageView userPhotoView) { return new EditUserPhotoController(activity, activityStarter, userPhotoView, - mSavedPhoto, mFileAuthority); + mSavedPhoto, mSavedDrawable, mFileAuthority); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java index 5862f6095018..38cf383645aa 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java @@ -62,7 +62,7 @@ public class EditUserPhotoController { private Drawable mNewUserPhotoDrawable; public EditUserPhotoController(Activity activity, ActivityStarter activityStarter, - ImageView view, Bitmap bitmap, String fileAuthority) { + ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) { mActivity = activity; mActivityStarter = activityStarter; mFileAuthority = fileAuthority; @@ -71,7 +71,9 @@ public class EditUserPhotoController { mImagesDir.mkdir(); mImageView = view; mImageView.setOnClickListener(v -> showAvatarPicker()); - mNewUserPhotoBitmap = bitmap; + + mNewUserPhotoBitmap = savedBitmap; + mNewUserPhotoDrawable = savedDrawable; } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java index 8e448aa0eace..994c1ee5013f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java @@ -129,6 +129,28 @@ public class AppUtilsTest { assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull(); } + @Test + public void isAppInstalled_noAppEntry_shouldReturnFalse() { + assertThat(AppUtils.isAppInstalled(null)).isFalse(); + } + + @Test + public void isAppInstalled_hasAppEntryWithInstalledFlag_shouldReturnTrue() { + final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); + appEntry.info = new ApplicationInfo(); + appEntry.info.flags = ApplicationInfo.FLAG_INSTALLED; + + assertThat(AppUtils.isAppInstalled(appEntry)).isTrue(); + } + + @Test + public void isAppInstalled_hasAppEntryWithoutInstalledFlag_shouldReturnFalse() { + final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); + appEntry.info = new ApplicationInfo(); + + assertThat(AppUtils.isAppInstalled(appEntry)).isFalse(); + } + private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) { ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id); appEntry.label = "label"; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 0c6d40aa9910..5088533ccdd8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -1074,13 +1074,63 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration "); // Depending on device hardware, we may need to notify the user of a setting change SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration(); - if (!storedConfig.equals(configInCloud)) { + + if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) { Log.d(TAG, "restored ap configuration requires a conversion, notify the user"); WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this); } } } + private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud, + SoftApConfiguration storedConfig) { + // Check if the cloud configuration was modified when restored to the device. + // All elements of the configuration are compared except: + // 1. Persistent randomized MAC address (which is per device) + // 2. The flag indicating whether the configuration is "user modified" + return !(Objects.equals(configInCloud.getWifiSsid(), storedConfig.getWifiSsid()) + && Objects.equals(configInCloud.getBssid(), storedConfig.getBssid()) + && Objects.equals(configInCloud.getPassphrase(), storedConfig.getPassphrase()) + && configInCloud.isHiddenSsid() == storedConfig.isHiddenSsid() + && configInCloud.getChannels().toString().equals( + storedConfig.getChannels().toString()) + && configInCloud.getSecurityType() == storedConfig.getSecurityType() + && configInCloud.getMaxNumberOfClients() == storedConfig.getMaxNumberOfClients() + && configInCloud.isAutoShutdownEnabled() == storedConfig.isAutoShutdownEnabled() + && configInCloud.getShutdownTimeoutMillis() + == storedConfig.getShutdownTimeoutMillis() + && configInCloud.isClientControlByUserEnabled() + == storedConfig.isClientControlByUserEnabled() + && Objects.equals(configInCloud.getBlockedClientList(), + storedConfig.getBlockedClientList()) + && Objects.equals(configInCloud.getAllowedClientList(), + storedConfig.getAllowedClientList()) + && configInCloud.getMacRandomizationSetting() + == storedConfig.getMacRandomizationSetting() + && configInCloud.isBridgedModeOpportunisticShutdownEnabled() + == storedConfig.isBridgedModeOpportunisticShutdownEnabled() + && configInCloud.isIeee80211axEnabled() == storedConfig.isIeee80211axEnabled() + && configInCloud.isIeee80211beEnabled() == storedConfig.isIeee80211beEnabled() + && configInCloud.getBridgedModeOpportunisticShutdownTimeoutMillis() + == storedConfig.getBridgedModeOpportunisticShutdownTimeoutMillis() + && Objects.equals(configInCloud.getVendorElements(), + storedConfig.getVendorElements()) + && (configInCloud.getPersistentRandomizedMacAddress() != null + ? Objects.equals(configInCloud.getPersistentRandomizedMacAddress(), + storedConfig.getPersistentRandomizedMacAddress()) : true) + && Arrays.equals(configInCloud.getAllowedAcsChannels( + SoftApConfiguration.BAND_2GHZ), + storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ)) + && Arrays.equals(configInCloud.getAllowedAcsChannels( + SoftApConfiguration.BAND_5GHZ), + storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_5GHZ)) + && Arrays.equals(configInCloud.getAllowedAcsChannels( + SoftApConfiguration.BAND_6GHZ), + storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_6GHZ)) + && configInCloud.getMaxChannelBandwidth() == storedConfig.getMaxChannelBandwidth() + ); + } + private byte[] getNetworkPolicies() { NetworkPolicyManager networkPolicyManager = (NetworkPolicyManager) getSystemService(NETWORK_POLICY_SERVICE); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index aadfcea150f7..a6edb0f0e2e3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -5509,16 +5509,7 @@ public class SettingsProvider extends ContentProvider { currentVersion = 209; } if (currentVersion == 209) { - // Version 209: Enable enforcement of - // android.Manifest.permission#POST_NOTIFICATIONS in order for applications - // to post notifications. - final SettingsState secureSettings = getSecureSettingsLocked(userId); - secureSettings.insertSettingLocked( - Secure.NOTIFICATION_PERMISSION_ENABLED, - /* enabled= */ "1", - /* tag= */ null, - /* makeDefault= */ false, - SettingsState.SYSTEM_PACKAGE_NAME); + // removed now that feature is enabled for everyone currentVersion = 210; } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 290ce345694e..4d0888ab8d2d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -323,6 +323,8 @@ <!-- To read safety center status --> <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" /> + <uses-permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" /> + <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> diff --git a/packages/SystemUI/res/drawable/ic_chevron_icon.xml b/packages/SystemUI/res/drawable/ic_chevron_icon.xml index acbbbcb21503..d60cc8c74e80 100644 --- a/packages/SystemUI/res/drawable/ic_chevron_icon.xml +++ b/packages/SystemUI/res/drawable/ic_chevron_icon.xml @@ -15,14 +15,6 @@ ~ limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="18dp" - android:height="31dp" - android:viewportWidth="18" - android:viewportHeight="31"> - <path - android:pathData="M0.0061,27.8986L2.6906,30.5831L17.9219,15.3518L2.6906,0.1206L0.0061,2.8051L12.5338,15.3518" - android:strokeAlpha="0.7" - android:fillColor="#FFFFFF" - android:fillAlpha="0.7"/> -</vector> +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/white" android:pathData="M9.4,18 L8,16.6 12.6,12 8,7.4 9.4,6 15.4,12Z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume.xml b/packages/SystemUI/res/drawable/media_output_icon_volume.xml new file mode 100644 index 000000000000..fce4e0022c7a --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_icon_volume.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@color/media_dialog_item_main_content" + android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.125,8.475 15.812,9.575Q16.5,10.675 16.5,12Q16.5,13.325 15.812,14.4Q15.125,15.475 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/media_output_status_check.xml b/packages/SystemUI/res/drawable/media_output_status_check.xml index 5fbc42b245b8..3d64f83eabe3 100644 --- a/packages/SystemUI/res/drawable/media_output_status_check.xml +++ b/packages/SystemUI/res/drawable/media_output_status_check.xml @@ -21,6 +21,6 @@ android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path - android:fillColor="@color/media_dialog_item_main_content" - android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/> + android:fillColor="@android:color/white" + android:pathData="M12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM10.6,16.6 L17.65,9.55 16.25,8.15 10.6,13.8 7.75,10.95 6.35,12.35Z"/> </vector> diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml index 1122ca6bd4dc..1c09e81f92ca 100644 --- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml +++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml @@ -12,6 +12,7 @@ android:layout_height="48dp" android:layout_marginTop="8dp" android:layout_marginStart="12dp" + android:paddingHorizontal="16dp" android:background="@drawable/overlay_button_background" android:text="@string/clipboard_edit_text_done" app:layout_constraintStart_toStartOf="parent" diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 10bb6cbb95aa..1712b4876b31 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -67,9 +67,9 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="@dimen/overlay_offset_y" + android:layout_marginBottom="12dp" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="@id/actions_container_background" + app:layout_constraintBottom_toBottomOf="parent" android:elevation="7dp" app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml index 890dbe592fc7..c29e11bff624 100644 --- a/packages/SystemUI/res/layout/screenshot.xml +++ b/packages/SystemUI/res/layout/screenshot.xml @@ -29,18 +29,11 @@ android:clickable="true" android:importantForAccessibility="no"/> <ImageView - android:id="@+id/screenshot_actions_background" - android:layout_height="@dimen/overlay_bg_protection_height" - android:layout_width="match_parent" - android:layout_gravity="bottom" - android:alpha="0.0" - android:src="@drawable/overlay_actions_background_protection"/> - <ImageView android:id="@+id/screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" - android:elevation="@dimen/overlay_preview_elevation" + android:elevation="7dp" android:src="@android:color/white"/> <com.android.systemui.screenshot.ScreenshotSelectorView android:id="@+id/screenshot_selector" diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index c60609b06d38..9c027495aa1e 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -24,7 +24,7 @@ android:visibility="gone" android:layout_height="0dp" android:layout_width="0dp" - android:elevation="1dp" + android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" app:layout_constraintBottom_toBottomOf="@+id/actions_container" @@ -36,9 +36,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginBottom="4dp" android:paddingEnd="@dimen/overlay_action_container_padding_right" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" - android:elevation="1dp" + android:elevation="4dp" android:scrollbars="none" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" @@ -64,8 +65,8 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="@dimen/overlay_offset_y" - android:elevation="@dimen/overlay_preview_elevation" + android:layout_marginBottom="12dp" + android:elevation="7dp" android:alpha="0" android:background="@drawable/overlay_border" app:layout_constraintStart_toStartOf="parent" @@ -93,7 +94,7 @@ android:layout_margin="@dimen/overlay_border_width" android:layout_height="wrap_content" android:layout_gravity="center" - android:elevation="@dimen/overlay_preview_elevation" + android:elevation="7dp" android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" @@ -108,7 +109,7 @@ android:id="@+id/screenshot_dismiss_button" android:layout_width="@dimen/overlay_dismiss_button_tappable_size" android:layout_height="@dimen/overlay_dismiss_button_tappable_size" - android:elevation="@dimen/overlay_dismiss_button_elevation" + android:elevation="10dp" android:visibility="gone" app:layout_constraintStart_toEndOf="@id/screenshot_preview" app:layout_constraintEnd_toEndOf="@id/screenshot_preview" @@ -130,5 +131,5 @@ android:visibility="gone" app:layout_constraintStart_toStartOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" - android:elevation="@dimen/overlay_preview_elevation"/> + android:elevation="7dp"/> </com.android.systemui.screenshot.DraggableConstraintLayout> diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index 1d6f279afc66..e6af6f46ae69 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -16,7 +16,7 @@ <resources> <!-- Minimum margin between clock and top of screen or ambient indication --> - <dimen name="keyguard_clock_top_margin">38dp</dimen> + <dimen name="keyguard_clock_top_margin">26dp</dimen> <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) --> <dimen name="large_clock_text_size">200dp</dimen> diff --git a/packages/SystemUI/res/values/defaults.xml b/packages/SystemUI/res/values/defaults.xml deleted file mode 100644 index f96c178b119a..000000000000 --- a/packages/SystemUI/res/values/defaults.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/** - * Copyright (c) 2009, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<resources> - <!-- Default for SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER. - To be set if the device wants to support out of the box QR code scanning experience --> - <string name="def_qr_code_component" translatable="false"></string> -</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a014efb7d176..0bc3594ef183 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -282,15 +282,12 @@ <!-- Spacing between chip icon and chip text --> <dimen name="overlay_action_chip_spacing">8dp</dimen> <dimen name="overlay_action_chip_text_size">14sp</dimen> - <dimen name="overlay_offset_y">8dp</dimen> <dimen name="overlay_offset_x">16dp</dimen> - <dimen name="overlay_preview_elevation">4dp</dimen> <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> <dimen name="overlay_action_container_corner_radius">18dp</dimen> <dimen name="overlay_action_container_padding_vertical">4dp</dimen> <dimen name="overlay_action_container_padding_right">8dp</dimen> - <dimen name="overlay_dismiss_button_elevation">7dp</dimen> <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen> <dimen name="overlay_dismiss_button_margin">8dp</dimen> <dimen name="overlay_border_width">4dp</dimen> @@ -1134,7 +1131,7 @@ <!-- Output switcher panel related dimensions --> <dimen name="media_output_dialog_list_margin">12dp</dimen> - <dimen name="media_output_dialog_list_max_height">364dp</dimen> + <dimen name="media_output_dialog_list_max_height">355dp</dimen> <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen> <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen> <dimen name="media_output_dialog_header_icon_padding">16dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2426f017e20e..2c3d947ba9e2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2484,6 +2484,12 @@ <string name="clipboard_send_nearby_description">Send to nearby device</string> <!-- Text informing user that copied content is hidden [CHAR LIMIT=NONE] --> <string name="clipboard_text_hidden">Tap to view</string> + <!-- Accessibility announcement informing user that text has been copied [CHAR LIMIT=NONE] --> + <string name="clipboard_text_copied">Text copied</string> + <!-- Accessibility announcement informing user that text has been copied [CHAR LIMIT=NONE] --> + <string name="clipboard_image_copied">Image copied</string> + <!-- Accessibility announcement informing user that something has been copied [CHAR LIMIT=NONE] --> + <string name="clipboard_content_copied">Content copied</string> <!-- Generic "add" string [CHAR LIMIT=NONE] --> <string name="add">Add</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java index 6345d113faed..1d6a3bf3b62e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java @@ -92,9 +92,18 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, } }; + /** + * @deprecated Pass a main executor. + */ public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback, Executor backgroundExecutor) { this(sampledView, samplingCallback, sampledView.getContext().getMainExecutor(), + backgroundExecutor); + } + + public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback, + Executor mainExecutor, Executor backgroundExecutor) { + this(sampledView, samplingCallback, mainExecutor, backgroundExecutor, new SysuiCompositionSamplingListener()); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index f8c0590a8d75..cce516d981a5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -15,6 +15,8 @@ */ package com.android.keyguard; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; @@ -32,6 +34,7 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -676,7 +679,11 @@ public class KeyguardSecurityContainer extends FrameLayout { attempts, remaining); break; case USER_TYPE_WORK_PROFILE: - message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_profile, + message = mContext.getSystemService(DevicePolicyManager.class).getResources() + .getString(KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE, + () -> mContext.getString( + R.string.kg_failed_attempts_almost_at_erase_profile, + attempts, remaining), attempts, remaining); break; } @@ -695,7 +702,10 @@ public class KeyguardSecurityContainer extends FrameLayout { attempts); break; case USER_TYPE_WORK_PROFILE: - message = mContext.getString(R.string.kg_failed_attempts_now_erasing_profile, + message = mContext.getSystemService(DevicePolicyManager.class).getResources() + .getString(KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, + () -> mContext.getString( + R.string.kg_failed_attempts_now_erasing_profile, attempts), attempts); break; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 121ac299ec5b..e0f1b657e48c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -188,6 +188,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_KEYGUARD_GOING_AWAY = 342; private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; + private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346; /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -2005,6 +2006,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_REQUIRE_NFC_UNLOCK: handleRequireUnlockForNfc(); break; + case MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED: + handleKeyguardDismissAnimationFinished(); + break; default: super.handleMessage(msg); break; @@ -2795,7 +2799,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Note: checking fingerprint enrollment directly with the AuthController requires an IPC. */ public boolean getCachedIsUnlockWithFingerprintPossible(int userId) { - return mIsUnlockWithFingerprintPossible.get(userId); + return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); } private boolean isUnlockWithFacePossible(int userId) { @@ -3277,6 +3281,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Handle {@link #MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED} + */ + private void handleKeyguardDismissAnimationFinished() { + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onKeyguardDismissAnimationFinished(); + } + } + } + + /** * Handle {@link #MSG_REPORT_EMERGENCY_CALL_ACTION} */ private void handleReportEmergencyCallAction() { @@ -3614,6 +3631,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.sendMessage(mHandler.obtainMessage(MSG_KEYGUARD_GOING_AWAY, goingAway)); } + /** + * Sends a message to notify the keyguard dismiss animation is finished. + */ + public void dispatchKeyguardDismissAnimationFinished() { + mHandler.sendEmptyMessage(MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED); + } + public boolean isDeviceInteractive() { return mDeviceInteractive; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 2620195ae8c4..051b81e484d8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -109,6 +109,14 @@ public class KeyguardUpdateMonitorCallback { public void onKeyguardBouncerFullyShowingChanged(boolean bouncerIsFullyShowing) { } /** + * Called when the dismissing animation of keyguard and surfaces behind is finished. + * If the surface behind is the Launcher, we may still be playing in-window animations + * when this is called (since it's only called once we dismiss the keyguard and end the + * remote animation). + */ + public void onKeyguardDismissAnimationFinished() { } + + /** * Called when visibility of lockscreen clock changes, such as when * obscured by a widget. */ diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index d79b1454514e..ab831be0f8e0 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -123,7 +123,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private float mHeightPixels; private float mWidthPixels; private int mBottomPaddingPx; - private int mScaledPaddingPx; + private int mDefaultPaddingPx; private boolean mShowUnlockIcon; private boolean mShowLockIcon; @@ -340,6 +340,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mWidthPixels = bounds.right; mHeightPixels = bounds.bottom; mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); + mDefaultPaddingPx = + getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); mUnlockedLabel = mView.getContext().getResources().getString( R.string.accessibility_unlock_button); @@ -348,17 +350,16 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private void updateLockIconLocation() { + final float scaleFactor = mAuthController.getScaleFactor(); + final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); if (mUdfpsSupported) { - final int defaultPaddingPx = - getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); - mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor()); mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), mScaledPaddingPx); + mAuthController.getUdfpsRadius(), scaledPadding); } else { mView.setCenterLocation( new PointF(mWidthPixels / 2, - mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx), - sLockIconRadiusPx, mScaledPaddingPx); + mHeightPixels - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor)), + sLockIconRadiusPx * scaleFactor, scaledPadding); } } diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index e51a63fbcd10..032a27a6fbcd 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -54,7 +54,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField val displayInfo = DisplayInfo() - @JvmField protected var pendingRotationChange = false + @JvmField protected var pendingConfigChange = false @JvmField protected val paint = Paint() @JvmField protected val cutoutPath = Path() @@ -145,7 +145,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) open fun updateCutout() { - if (pendingRotationChange) { + if (pendingConfigChange) { return } cutoutPath.reset() @@ -225,7 +225,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { } protected open fun updateProtectionBoundingPath() { - if (pendingRotationChange) { + if (pendingConfigChange) { return } val m = Matrix() diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index dd312186afee..685c585a52be 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -53,6 +53,7 @@ import android.provider.Settings.Secure; import android.util.DisplayUtils; import android.util.Log; import android.util.Size; +import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayCutout.BoundsPosition; import android.view.DisplayInfo; @@ -151,12 +152,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private SettingObserver mColorInversionSetting; private DelayableExecutor mExecutor; private Handler mHandler; - boolean mPendingRotationChange; + boolean mPendingConfigChange; @VisibleForTesting String mDisplayUniqueId; private int mTintColor = Color.BLACK; @VisibleForTesting protected DisplayDecorationSupport mHwcScreenDecorationSupport; + private Display.Mode mDisplayMode; private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback = new CameraAvailabilityListener.CameraTransitionCallback() { @@ -324,6 +326,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mWindowManager = mContext.getSystemService(WindowManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); mRotation = mContext.getDisplay().getRotation(); + mDisplayMode = mContext.getDisplay().getMode(); mDisplayUniqueId = mContext.getDisplay().getUniqueId(); mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(), mDisplayUniqueId); @@ -349,8 +352,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @Override public void onDisplayChanged(int displayId) { final int newRotation = mContext.getDisplay().getRotation(); + final Display.Mode newDisplayMode = mContext.getDisplay().getMode(); if ((mOverlays != null || mScreenDecorHwcWindow != null) - && mRotation != newRotation) { + && (mRotation != newRotation + || displayModeChanged(mDisplayMode, newDisplayMode))) { // We cannot immediately update the orientation. Otherwise // WindowManager is still deferring layout until it has finished dispatching // the config changes, which may cause divergence between what we draw @@ -358,10 +363,16 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab // Instead we wait until either: // - we are trying to redraw. This because WM resized our window and told us to. // - the config change has been dispatched, so WM is no longer deferring layout. - mPendingRotationChange = true; + mPendingConfigChange = true; if (DEBUG) { - Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at " - + mRotation); + if (mRotation != newRotation) { + Log.i(TAG, "Rotation changed, deferring " + newRotation + + ", staying at " + mRotation); + } + if (displayModeChanged(mDisplayMode, newDisplayMode)) { + Log.i(TAG, "Resolution changed, deferring " + newDisplayMode + + ", staying at " + mDisplayMode); + } } if (mOverlays != null) { @@ -369,7 +380,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays[i] != null) { final ViewGroup overlayView = mOverlays[i].getRootView(); overlayView.getViewTreeObserver().addOnPreDrawListener( - new RestartingPreDrawListener(overlayView, i, newRotation)); + new RestartingPreDrawListener( + overlayView, i, newRotation, newDisplayMode)); } } } @@ -379,7 +391,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab new RestartingPreDrawListener( mScreenDecorHwcWindow, -1, // Pass -1 for views with no specific position. - newRotation)); + newRotation, newDisplayMode)); + } + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.pendingConfigChange = true; } } @@ -435,7 +450,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab }; mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); - updateOrientation(); + updateConfiguration(); } @Nullable @@ -807,6 +822,17 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } + private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) { + if (oldMode == null) { + return true; + } + + // We purposely ignore refresh rate and id changes here, because we don't need to + // invalidate for those, and they can trigger the refresh rate to increase + return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth() + || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight(); + } + private int getOverlayWindowGravity(@BoundsPosition int pos) { final int rotated = getBoundPositionFromRotation(pos, mRotation); switch (rotated) { @@ -913,8 +939,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mExecutor.execute(() -> { int oldRotation = mRotation; - mPendingRotationChange = false; - updateOrientation(); + mPendingConfigChange = false; + updateConfiguration(); if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation); setupDecorations(); if (mOverlays != null) { @@ -941,7 +967,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS); pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled()); pw.println(" isOnlyPrivacyDotInSwLayer:" + isOnlyPrivacyDotInSwLayer()); - pw.println(" mPendingRotationChange:" + mPendingRotationChange); + pw.println(" mPendingConfigChange:" + mPendingConfigChange); if (mHwcScreenDecorationSupport != null) { pw.println(" mHwcScreenDecorationSupport:"); pw.println(" format=" @@ -957,15 +983,23 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } else { pw.println(" mScreenDecorHwcLayer: null"); } - pw.println(" mOverlays(left,top,right,bottom)=(" - + (mOverlays != null && mOverlays[BOUNDS_POSITION_LEFT] != null) + "," - + (mOverlays != null && mOverlays[BOUNDS_POSITION_TOP] != null) + "," - + (mOverlays != null && mOverlays[BOUNDS_POSITION_RIGHT] != null) + "," - + (mOverlays != null && mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")"); + if (mOverlays != null) { + pw.println(" mOverlays(left,top,right,bottom)=(" + + (mOverlays[BOUNDS_POSITION_LEFT] != null) + "," + + (mOverlays[BOUNDS_POSITION_TOP] != null) + "," + + (mOverlays[BOUNDS_POSITION_RIGHT] != null) + "," + + (mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")"); + + for (int i = BOUNDS_POSITION_LEFT; i < BOUNDS_POSITION_LENGTH; i++) { + if (mOverlays[i] != null) { + mOverlays[i].dump(pw, getWindowTitleByPos(i)); + } + } + } mRoundedCornerResDelegate.dump(pw, args); } - private void updateOrientation() { + private void updateConfiguration() { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() + ", but was " + Thread.currentThread()); @@ -974,11 +1008,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mRotation != newRotation) { mDotViewController.setNewRotation(newRotation); } + final Display.Mode newMod = mContext.getDisplay().getMode(); - if (!mPendingRotationChange && newRotation != mRotation) { + if (!mPendingConfigChange + && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) { mRotation = newRotation; + mDisplayMode = newMod; if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.pendingRotationChange = false; + mScreenDecorHwcLayer.pendingConfigChange = false; mScreenDecorHwcLayer.updateRotation(mRotation); updateHwLayerRoundedCornerExistAndSize(); updateHwLayerRoundedCornerDrawable(); @@ -1189,7 +1226,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @Override public void updateCutout() { - if (!isAttachedToWindow() || pendingRotationChange) { + if (!isAttachedToWindow() || pendingConfigChange) { return; } mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation); @@ -1330,40 +1367,47 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private final View mView; private final int mTargetRotation; + private final Display.Mode mTargetDisplayMode; // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific // position. private final int mPosition; private RestartingPreDrawListener(View view, @BoundsPosition int position, - int targetRotation) { + int targetRotation, Display.Mode targetDisplayMode) { mView = view; mTargetRotation = targetRotation; + mTargetDisplayMode = targetDisplayMode; mPosition = position; } @Override public boolean onPreDraw() { mView.getViewTreeObserver().removeOnPreDrawListener(this); - - if (mTargetRotation == mRotation) { + if (mTargetRotation == mRotation + && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) { if (DEBUG) { final String title = mPosition < 0 ? "ScreenDecorHwcLayer" : getWindowTitleByPos(mPosition); Log.i(TAG, title + " already in target rot " - + mTargetRotation + ", allow draw without restarting it"); + + mTargetRotation + " and in target resolution " + + mTargetDisplayMode.getPhysicalWidth() + "x" + + mTargetDisplayMode.getPhysicalHeight() + + ", allow draw without restarting it"); } return true; } - mPendingRotationChange = false; + mPendingConfigChange = false; // This changes the window attributes - we need to restart the traversal for them to // take effect. - updateOrientation(); + updateConfiguration(); if (DEBUG) { final String title = mPosition < 0 ? "ScreenDecorHwcLayer" : getWindowTitleByPos(mPosition); Log.i(TAG, title - + " restarting listener fired, restarting draw for rot " + mRotation); + + " restarting listener fired, restarting draw for rot " + mRotation + + ", resolution " + mDisplayMode.getPhysicalWidth() + "x" + + mDisplayMode.getPhysicalHeight()); } mView.invalidate(); return false; @@ -1371,8 +1415,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } /** - * A pre-draw listener, that validates that the rotation we draw in matches the displays - * rotation before continuing the draw. + * A pre-draw listener, that validates that the rotation and display resolution we draw in + * matches the display's rotation and resolution before continuing the draw. * * This is to prevent a race condition, where we have not received the display changed event * yet, and would thus draw in an old orientation. @@ -1388,10 +1432,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @Override public boolean onPreDraw() { final int displayRotation = mContext.getDisplay().getRotation(); - if (displayRotation != mRotation && !mPendingRotationChange) { + final Display.Mode displayMode = mContext.getDisplay().getMode(); + if (displayRotation != mRotation && displayModeChanged(mDisplayMode, displayMode) + && !mPendingConfigChange) { if (DEBUG) { - Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot " - + displayRotation + ". Restarting draw"); + if (displayRotation != mRotation) { + Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot " + + displayRotation + ". Restarting draw"); + } + if (displayModeChanged(mDisplayMode, displayMode)) { + Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth() + + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at " + + displayMode.getPhysicalWidth() + "x" + + displayMode.getPhysicalHeight() + ". Restarting draw"); + } } mView.invalidate(); return false; diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 56fbe6d9bf14..6b859763eb6f 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -102,6 +102,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_SYSTEM_ALERT_WINDOW, AppOpsManager.OP_RECORD_AUDIO, + AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION @@ -375,7 +376,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName, Boolean.toString(active), attributionChainId, attributionFlags)); } - if (attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE + if (active && attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE && attributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR) == 0 && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_TRUSTED) == 0) { @@ -535,7 +536,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon } private boolean isOpMicrophone(int op) { - return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE; + return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE + || op == AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; } protected class H extends Handler { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt index 4cd40d2f186b..203578123999 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt @@ -15,9 +15,11 @@ */ package com.android.systemui.biometrics +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager /** @@ -28,6 +30,7 @@ class UdfpsBpViewController( statusBarStateController: StatusBarStateController, panelExpansionStateManager: PanelExpansionStateManager, systemUIDialogManager: SystemUIDialogManager, + val broadcastSender: BroadcastSender, dumpManager: DumpManager ) : UdfpsAnimationViewController<UdfpsBpView>( view, @@ -37,4 +40,29 @@ class UdfpsBpViewController( dumpManager ) { override val tag = "UdfpsBpViewController" + private val bpPanelExpansionListener = PanelExpansionListener { event -> + // Notification shade can be expanded but not visible (fraction: 0.0), for example + // when a heads-up notification (HUN) is showing. + notificationShadeVisible = event.expanded && event.fraction > 0f + view.onExpansionChanged(event.fraction) + cancelAuth() + } + + fun cancelAuth() { + if (shouldPauseAuth()) { + broadcastSender.closeSystemDialogs() + } + } + + override fun onViewAttached() { + super.onViewAttached() + + panelExpansionStateManager.addExpansionListener(bpPanelExpansionListener) + } + + override fun onViewDetached() { + super.onViewDetached() + + panelExpansionStateManager.removeExpansionListener(bpPanelExpansionListener) + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index a35f0427e55a..7657269643aa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -54,6 +54,7 @@ import com.android.internal.util.LatencyTracker; import com.android.keyguard.ActiveUnlockConfig; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; @@ -125,6 +126,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @NonNull private final LatencyTracker mLatencyTracker; + @NonNull private final BroadcastSender mBroadcastSender; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; @@ -205,7 +207,7 @@ public class UdfpsController implements DozeReceiver { mUnlockedScreenOffAnimationController, mHalControlsIllumination, mHbmProvider, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, - fromUdfpsView), mActivityLaunchAnimator))); + fromUdfpsView), mActivityLaunchAnimator, mBroadcastSender))); } @Override @@ -574,7 +576,8 @@ public class UdfpsController implements DozeReceiver { @NonNull SystemUIDialogManager dialogManager, @NonNull LatencyTracker latencyTracker, @NonNull ActivityLaunchAnimator activityLaunchAnimator, - @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider) { + @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider, + @NonNull BroadcastSender broadcastSender) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -604,6 +607,7 @@ public class UdfpsController implements DozeReceiver { mLatencyTracker = latencyTracker; mActivityLaunchAnimator = activityLaunchAnimator; mAlternateTouchProvider = aternateTouchProvider.orElse(null); + mBroadcastSender = broadcastSender; mOrientationListener = new BiometricDisplayListener( context, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 2d51c973b0b1..faa93a5a2ad3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -41,6 +41,7 @@ import androidx.annotation.LayoutRes import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -83,7 +84,8 @@ class UdfpsControllerOverlay( @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityLaunchAnimator: ActivityLaunchAnimator + private val activityLaunchAnimator: ActivityLaunchAnimator, + private val broadcastSender: BroadcastSender ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -102,8 +104,8 @@ class UdfpsControllerOverlay( fitInsetsTypes = 0 gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - flags = - (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) + flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS + or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY // Avoid announcing window title. accessibilityTitle = " " @@ -221,6 +223,7 @@ class UdfpsControllerOverlay( statusBarStateController, panelExpansionStateManager, dialogManager, + broadcastSender, dumpManager ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index 9139699af26a..f28fedb9155b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -32,6 +32,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; @@ -44,6 +45,8 @@ import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.model.KeyPath; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * View corresponding with udfps_keyguard_view.xml @@ -52,7 +55,6 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { private UdfpsDrawable mFingerprintDrawable; // placeholder private LottieAnimationView mAodFp; private LottieAnimationView mLockScreenFp; - private int mStatusBarState; // used when highlighting fp icon: private int mTextColorPrimary; @@ -70,7 +72,7 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { private float mBurnInOffsetY; private float mBurnInProgress; private float mInterpolatedDarkAmount; - private boolean mAnimatingBetweenAodAndLockscreen; // As opposed to Unlocked => AOD + private int mAnimationType = ANIMATION_NONE; private boolean mFullyInflated; public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) { @@ -117,8 +119,10 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { return; } - final float darkAmountForAnimation = mAnimatingBetweenAodAndLockscreen - ? mInterpolatedDarkAmount : 1f /* animating from unlocked to AOD */; + // if we're animating from screen off, we can immediately place the icon in the + // AoD-burn in location, else we need to translate the icon from LS => AoD. + final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF + ? 1f : mInterpolatedDarkAmount; mBurnInOffsetX = MathUtils.lerp(0f, getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - mMaxBurnInOffsetX, darkAmountForAnimation); @@ -127,12 +131,12 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { - mMaxBurnInOffsetY, darkAmountForAnimation); mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation); - if (mAnimatingBetweenAodAndLockscreen && !mPauseAuth) { + if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) { mLockScreenFp.setTranslationX(mBurnInOffsetX); mLockScreenFp.setTranslationY(mBurnInOffsetY); mBgProtection.setAlpha(1f - mInterpolatedDarkAmount); mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount); - } else if (mInterpolatedDarkAmount == 0f) { + } else if (darkAmountForAnimation == 0f) { mLockScreenFp.setTranslationX(0); mLockScreenFp.setTranslationY(0); mBgProtection.setAlpha(mAlpha / 255f); @@ -148,9 +152,15 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { mAodFp.setProgress(mBurnInProgress); mAodFp.setAlpha(mInterpolatedDarkAmount); - // done animating between AoD & LS - if (mInterpolatedDarkAmount == 1f || mInterpolatedDarkAmount == 0f) { - mAnimatingBetweenAodAndLockscreen = false; + // done animating + final boolean doneAnimatingBetweenAodAndLS = + mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN + && (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f); + final boolean doneAnimatingUnlockedScreenOff = + mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF + && (mInterpolatedDarkAmount == 1f); + if (doneAnimatingBetweenAodAndLS || doneAnimatingUnlockedScreenOff) { + mAnimationType = ANIMATION_NONE; } } @@ -158,10 +168,6 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { mUdfpsRequested = request; } - void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; - } - void updateColor() { if (!mFullyInflated) { return; @@ -219,8 +225,16 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { return mAlpha; } - void onDozeAmountChanged(float linear, float eased, boolean animatingBetweenAodAndLockscreen) { - mAnimatingBetweenAodAndLockscreen = animatingBetweenAodAndLockscreen; + static final int ANIMATION_NONE = 0; + static final int ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN = 1; + static final int ANIMATION_UNLOCKED_SCREEN_OFF = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ANIMATION_NONE, ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, ANIMATION_UNLOCKED_SCREEN_OFF}) + private @interface AnimationType {} + + void onDozeAmountChanged(float linear, float eased, @AnimationType int animationType) { + mAnimationType = animationType; mInterpolatedDarkAmount = eased; updateAlpha(); } @@ -262,7 +276,7 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { pw.println(" mUnpausedAlpha=" + getUnpausedAlpha()); pw.println(" mUdfpsRequested=" + mUdfpsRequested); pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount); - pw.println(" mAnimatingBetweenAodAndLockscreen=" + mAnimatingBetweenAodAndLockscreen); + pw.println(" mAnimationType=" + mAnimationType); } private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 8b0f36fe1245..ec4cf2fd8bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -121,7 +121,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mView.onDozeAmountChanged( animation.getAnimatedFraction(), (float) animation.getAnimatedValue(), - /* animatingBetweenAodAndLockScreen */ false); + UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF); } }); } @@ -394,7 +394,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mUnlockedScreenOffDozeAnimator.start(); } else { mView.onDozeAmountChanged(linear, eased, - /* animatingBetweenAodAndLockScreen */ true); + UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN); } mLastDozeAmount = linear; @@ -404,7 +404,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @Override public void onStateChanged(int statusBarState) { mStatusBarState = statusBarState; - mView.setStatusBarState(statusBarState); updateAlpha(); updatePauseAuth(); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 6a9317f2b543..f526277a0a37 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -20,10 +20,14 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED; +import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.os.SystemProperties; import android.provider.DeviceConfig; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; @@ -37,6 +41,13 @@ import javax.inject.Inject; @SysUISingleton public class ClipboardListener extends CoreStartable implements ClipboardManager.OnPrimaryClipChangedListener { + private static final String TAG = "ClipboardListener"; + + @VisibleForTesting + static final String SHELL_PACKAGE = "com.android.shell"; + @VisibleForTesting + static final String EXTRA_SUPPRESS_OVERLAY = + "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY"; private final DeviceConfigProxy mDeviceConfig; private final ClipboardOverlayControllerFactory mOverlayFactory; @@ -68,18 +79,44 @@ public class ClipboardListener extends CoreStartable if (!mClipboardManager.hasPrimaryClip()) { return; } + String clipSource = mClipboardManager.getPrimaryClipSource(); + ClipData clipData = mClipboardManager.getPrimaryClip(); + + if (shouldSuppressOverlay(clipData, clipSource, isEmulator())) { + Log.i(TAG, "Clipboard overlay suppressed."); + return; + } + if (mClipboardOverlayController == null) { mClipboardOverlayController = mOverlayFactory.create(mContext); mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource); } else { mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource); } - mClipboardOverlayController.setClipData( - mClipboardManager.getPrimaryClip(), clipSource); + mClipboardOverlayController.setClipData(clipData, clipSource); mClipboardOverlayController.setOnSessionCompleteListener(() -> { // Session is complete, free memory until it's needed again. mClipboardOverlayController = null; }); } + + // The overlay is suppressed if EXTRA_SUPPRESS_OVERLAY is true and the device is an emulator or + // the source package is SHELL_PACKAGE. This is meant to suppress the overlay when the emulator + // or a mirrored device is syncing the clipboard. + @VisibleForTesting + static boolean shouldSuppressOverlay(ClipData clipData, String clipSource, + boolean isEmulator) { + if (!(isEmulator || SHELL_PACKAGE.equals(clipSource))) { + return false; + } + if (clipData == null || clipData.getDescription().getExtras() == null) { + return false; + } + return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false); + } + + private static boolean isEmulator() { + return SystemProperties.getBoolean("ro.boot.qemu", false); + } } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index ee8363fe50d4..d8f4fa4096d0 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -35,6 +35,7 @@ import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.MainThread; +import android.app.ICompatCameraControlCallback; import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ClipData; @@ -44,7 +45,9 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; @@ -68,6 +71,7 @@ import android.view.InputMonitor; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewRootImpl; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; @@ -85,6 +89,7 @@ import android.widget.TextView; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; +import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; @@ -150,6 +155,10 @@ public class ClipboardOverlayController { private boolean mBlockAttach = false; private Animator mExitAnimator; + /** Tracks config changes that require updating insets */ + private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( + ActivityInfo.CONFIG_KEYBOARD_HIDDEN); + public ClipboardOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, @@ -232,6 +241,24 @@ public class ClipboardOverlayController { mWindow.setContentView(mView); updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets()); mView.requestLayout(); + mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( + new ViewRootImpl.ActivityConfigCallback() { + @Override + public void onConfigurationChanged(Configuration overrideConfig, + int newDisplayId) { + if (mConfigChanges.applyNewConfig(mContext.getResources())) { + updateInsets( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + } + } + + @Override + public void requestCompatCameraControl( + boolean showControl, boolean transformationApplied, + ICompatCameraControlCallback callback) { + Log.w(TAG, "unexpected requestCompatCameraControl call"); + } + }); }); mTimeoutHandler.setOnTimeoutRunnable(() -> { @@ -275,6 +302,7 @@ public class ClipboardOverlayController { mExitAnimator.cancel(); } reset(); + String accessibilityAnnouncement; boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null && clipData.getDescription().getExtras() @@ -283,6 +311,7 @@ public class ClipboardOverlayController { showTextPreview( mContext.getResources().getString(R.string.clipboard_overlay_text_copied), mTextPreview); + accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { ClipData.Item item = clipData.getItemAt(0); if (item.getTextLinks() != null) { @@ -294,13 +323,18 @@ public class ClipboardOverlayController { } else { showEditableText(item.getText(), false); } + accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied); } else if (clipData.getItemAt(0).getUri() != null) { - // How to handle non-image URIs? - showEditableImage(clipData.getItemAt(0).getUri(), isSensitive); + if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) { + accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied); + } else { + accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); + } } else { showTextPreview( mContext.getResources().getString(R.string.clipboard_overlay_text_copied), mTextPreview); + accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); } Intent remoteCopyIntent = getRemoteCopyIntent(clipData); // Only show remote copy if it's available. @@ -317,7 +351,12 @@ public class ClipboardOverlayController { } else { mRemoteCopyChip.setVisibility(View.GONE); } - withWindowAttached(() -> mView.post(this::animateIn)); + withWindowAttached(() -> { + updateInsets( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + mView.post(this::animateIn); + mView.announceForAccessibility(accessibilityAnnouncement); + }); mTimeoutHandler.resetTimeout(); } @@ -449,33 +488,46 @@ public class ClipboardOverlayController { textView.setOnClickListener(listener); } - private void showEditableImage(Uri uri, boolean isSensitive) { - mEditChip.setAlpha(1f); - mActionContainerBackground.setVisibility(View.VISIBLE); + private boolean tryShowEditableImage(Uri uri, boolean isSensitive) { View.OnClickListener listener = v -> editImage(uri); + ContentResolver resolver = mContext.getContentResolver(); + String mimeType = resolver.getType(uri); + boolean isEditableImage = mimeType != null && mimeType.startsWith("image"); if (isSensitive) { showSinglePreview(mHiddenImagePreview); - mHiddenImagePreview.setOnClickListener(listener); - } else { - showSinglePreview(mImagePreview); - ContentResolver resolver = mContext.getContentResolver(); + if (isEditableImage) { + mHiddenImagePreview.setOnClickListener(listener); + } + } 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); + showSinglePreview(mImagePreview); mImagePreview.setImageBitmap(thumbnail); + mImagePreview.setOnClickListener(listener); } catch (IOException e) { Log.e(TAG, "Thumbnail loading failed", e); showTextPreview( mContext.getResources().getString(R.string.clipboard_overlay_text_copied), mTextPreview); + isEditableImage = false; } - mImagePreview.setOnClickListener(listener); + } else { + showTextPreview( + mContext.getResources().getString(R.string.clipboard_overlay_text_copied), + mTextPreview); } - mEditChip.setOnClickListener(listener); - mEditChip.setContentDescription( - mContext.getString(R.string.clipboard_edit_image_description)); + if (isEditableImage) { + mEditChip.setVisibility(View.VISIBLE); + mEditChip.setAlpha(1f); + mActionContainerBackground.setVisibility(View.VISIBLE); + mEditChip.setOnClickListener(listener); + mEditChip.setContentDescription( + mContext.getString(R.string.clipboard_edit_image_description)); + } + return isEditableImage; } private Intent getRemoteCopyIntent(ClipData clipData) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index a4f9f3a9bc08..6a9aaf865251 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -472,7 +472,6 @@ class ControlViewHolder( updateContentDescription() status.setTextColor(color) - chevronIcon.imageTintList = color control?.getCustomIcon()?.let { icon.setImageIcon(it) @@ -495,10 +494,13 @@ class ControlViewHolder( icon.imageTintList = color } } + + chevronIcon.imageTintList = icon.imageTintList } private fun setEnabled(enabled: Boolean) { - status.setEnabled(enabled) - icon.setEnabled(enabled) + status.isEnabled = enabled + icon.isEnabled = enabled + chevronIcon.isEnabled = enabled } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt index d775ad39a5c3..3c0748e02552 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt @@ -16,11 +16,13 @@ package com.android.systemui.decor import android.annotation.IdRes +import android.annotation.NonNull import android.content.Context import android.view.Surface import android.view.View import android.view.ViewGroup import com.android.systemui.RegionInterceptingFrameLayout +import java.io.PrintWriter class OverlayWindow(private val context: Context) { @@ -100,4 +102,13 @@ class OverlayWindow(private val context: Context) { } } } + + fun dump(@NonNull pw: PrintWriter, name: String) { + pw.println(" $name=") + pw.println(" rootView=$rootView") + for (i in 0 until rootView.childCount) { + val child = rootView.getChildAt(i) + pw.println(" child[$i]=$child") + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index e963c39329ca..afa7d5e0a9c4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -151,7 +151,7 @@ public class Flags { /***************************************/ // 900 - media public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true); - public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true); + public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false); public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true); public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 14a7e3c7f013..404e5311961b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -857,6 +857,13 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** + * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. + */ + fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean { + return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind + } + + /** * Whether we are playing a canned unlock animation, vs. unlocking from a touch gesture such as * a swipe. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 4d59f1a8e363..e379d766f0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2625,6 +2625,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // The remote animation is over, so we're not going away anymore. mKeyguardStateController.notifyKeyguardGoingAway(false); + + // Dispatch the callback on animation finishes. + mUpdateMonitor.dispatchKeyguardDismissAnimationFinished(); }); mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation( diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index e16da89d6ec8..25effec42f1a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -89,7 +89,7 @@ class LogBuffer @JvmOverloads constructor( init { if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) { - thread(start = true, priority = Thread.NORM_PRIORITY) { + thread(start = true, name = "LogBuffer-$name", priority = Thread.NORM_PRIORITY) { try { while (true) { echoToDesiredEndpoints(echoMessageQueue.take()) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 3483bc39b943..8002fb88c41a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -845,7 +845,8 @@ class MediaCarouselController @Inject constructor( uid, interactedSubcardRank, interactedSubcardCardinality, - receivedLatencyMillis + receivedLatencyMillis, + null // Media cards cannot have subcards. ) /* ktlint-disable max-line-length */ if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d65940172b17..b9601420bb26 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -97,7 +97,7 @@ import kotlin.Unit; * A view controller used for Media Playback. */ public class MediaControlPanel { - private static final String TAG = "MediaControlPanel"; + protected static final String TAG = "MediaControlPanel"; private static final float DISABLED_ALPHA = 0.38f; private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google" @@ -106,7 +106,7 @@ public class MediaControlPanel { "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"; private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name"; private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"; - private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"; + protected static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"; // Event types logged by smartspace private static final int SMARTSPACE_CARD_CLICK_EVENT = 760; @@ -149,6 +149,7 @@ public class MediaControlPanel { private RecommendationViewHolder mRecommendationViewHolder; private String mKey; private MediaData mMediaData; + private SmartspaceMediaData mRecommendationData; private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; @@ -448,6 +449,7 @@ public class MediaControlPanel { bindOutputSwitcherChip(data); bindGutsMenuForPlayer(data); + bindPlayerContentDescription(data); bindScrubbingTime(data); bindActionButtons(data); @@ -541,12 +543,6 @@ public class MediaControlPanel { } private boolean bindSongMetadata(MediaData data) { - // Accessibility label - mMediaViewHolder.getPlayer().setContentDescription( - mContext.getString( - R.string.controls_media_playing_item_description, - data.getSong(), data.getArtist(), data.getApp())); - TextView titleText = mMediaViewHolder.getTitleText(); TextView artistText = mMediaViewHolder.getArtistText(); return mMetadataAnimationHandler.setNext( @@ -568,6 +564,48 @@ public class MediaControlPanel { }); } + // We may want to look into unifying this with bindRecommendationContentDescription if/when we + // do a refactor of this class. + private void bindPlayerContentDescription(MediaData data) { + if (mMediaViewHolder == null) { + return; + } + + CharSequence contentDescription; + if (mMediaViewController.isGutsVisible()) { + contentDescription = mMediaViewHolder.getGutsViewHolder().getGutsText().getText(); + } else if (data != null) { + contentDescription = mContext.getString( + R.string.controls_media_playing_item_description, + data.getSong(), + data.getArtist(), + data.getApp()); + } else { + contentDescription = null; + } + mMediaViewHolder.getPlayer().setContentDescription(contentDescription); + } + + private void bindRecommendationContentDescription(SmartspaceMediaData data) { + if (mRecommendationViewHolder == null) { + return; + } + + CharSequence contentDescription; + if (mMediaViewController.isGutsVisible()) { + contentDescription = + mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText(); + } else if (data != null) { + contentDescription = mContext.getString( + R.string.controls_media_smartspace_rec_description, + data.getAppName(mContext)); + } else { + contentDescription = null; + } + + mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription); + } + private void bindArtworkAndColors(MediaData data, boolean updateBackground) { final int reqId = mArtworkNextBindRequestId++; if (updateBackground) { @@ -636,10 +674,6 @@ public class MediaControlPanel { mIsArtworkBound = isArtworkBound; } - // Scrim bounds are set manually so it scales as expected - albumView.getForeground().setBounds(0, 0, - Math.max(width, height), Math.max(width, height)); - // Transition Colors to current color scheme mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound); @@ -954,6 +988,7 @@ public class MediaControlPanel { return; } + mRecommendationData = data; mSmartspaceId = SmallHash.hash(data.getTargetId()); mPackageName = data.getPackageName(); mInstanceId = data.getInstanceId(); @@ -969,6 +1004,12 @@ public class MediaControlPanel { return; } + CharSequence appName = data.getAppName(mContext); + if (appName == null) { + Log.w(TAG, "Fail to get media recommendation's app name"); + return; + } + PackageManager packageManager = mContext.getPackageManager(); // Set up media source app's logo. Drawable icon = packageManager.getApplicationIcon(applicationInfo); @@ -976,28 +1017,11 @@ public class MediaControlPanel { headerLogoImageView.setImageDrawable(icon); fetchAndUpdateRecommendationColors(icon); - // Set up media source app's label text. - CharSequence appName = getAppName(data.getCardAction()); - if (TextUtils.isEmpty(appName)) { - Intent launchIntent = - packageManager.getLaunchIntentForPackage(data.getPackageName()); - if (launchIntent != null) { - ActivityInfo launchActivity = launchIntent.resolveActivityInfo(packageManager, 0); - appName = launchActivity.loadLabel(packageManager); - } else { - Log.w(TAG, "Package " + data.getPackageName() - + " does not have a main launcher activity. Fallback to full app name"); - appName = packageManager.getApplicationLabel(applicationInfo); - } - } - // Set up media rec card's tap action if applicable. TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(), /* interactedSubcardRank */ -1); - // Set up media rec card's accessibility label. - recommendationCard.setContentDescription( - mContext.getString(R.string.controls_media_smartspace_rec_description, appName)); + bindRecommendationContentDescription(data); List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems(); List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers(); @@ -1179,6 +1203,11 @@ public class MediaControlPanel { mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION); } mMediaViewController.closeGuts(immediate); + if (mMediaViewHolder != null) { + bindPlayerContentDescription(mMediaData); + } else if (mRecommendationViewHolder != null) { + bindRecommendationContentDescription(mRecommendationData); + } } private void closeGuts() { @@ -1192,6 +1221,11 @@ public class MediaControlPanel { mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION); } mMediaViewController.openGuts(); + if (mMediaViewHolder != null) { + bindPlayerContentDescription(mMediaData); + } else if (mRecommendationViewHolder != null) { + bindRecommendationContentDescription(mRecommendationData); + } mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId); } @@ -1306,17 +1340,6 @@ public class MediaControlPanel { }); } - /** Returns the upstream app name if available. */ - @Nullable - private String getAppName(SmartspaceAction action) { - if (action == null || action.getIntent() == null - || action.getIntent().getExtras() == null) { - return null; - } - - return action.getIntent().getExtras().getString(KEY_SMARTSPACE_APP_NAME); - } - /** Returns if the Smartspace action will open the activity in foreground. */ private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) { if (action == null || action.getIntent() == null diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index f85078c24603..11ee6578e27d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -30,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor import javax.inject.Inject @@ -44,6 +45,7 @@ class MediaDeviceManager @Inject constructor( private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory, + private val configurationController: ConfigurationController, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, dumpManager: DumpManager @@ -79,7 +81,7 @@ class MediaDeviceManager @Inject constructor( oldEntry?.stop() } var entry = entries[key] - if (entry == null || entry?.token != data.token) { + if (entry == null || entry.token != data.token) { entry?.stop() if (data.device != null) { // If we were already provided device info (e.g. from RCN), keep that and don't @@ -118,10 +120,9 @@ class MediaDeviceManager @Inject constructor( override fun dump(pw: PrintWriter, args: Array<String>) { with(pw) { println("MediaDeviceManager state:") - entries.forEach { - key, entry -> + entries.forEach { (key, entry) -> println(" key=$key") - entry.dump(pw, args) + entry.dump(pw) } } } @@ -165,6 +166,12 @@ class MediaDeviceManager @Inject constructor( // expected to connect imminently, it should be displayed as the current device. private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null + private val configListener = object : ConfigurationController.ConfigurationListener { + override fun onLocaleListChanged() { + updateCurrent() + } + } + @AnyThread fun start() = bgExecutor.execute { localMediaManager.registerCallback(this) @@ -174,6 +181,7 @@ class MediaDeviceManager @Inject constructor( controller?.registerCallback(this) updateCurrent() started = true + configurationController.addCallback(configListener) } @AnyThread @@ -183,9 +191,10 @@ class MediaDeviceManager @Inject constructor( localMediaManager.stopScan() localMediaManager.unregisterCallback(this) muteAwaitConnectionManager?.stopListening() + configurationController.removeCallback(configListener) } - fun dump(pw: PrintWriter, args: Array<String>) { + fun dump(pw: PrintWriter) { val routingSession = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index 4f598ff797d0..40a5653a15a0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -52,8 +52,10 @@ public class ResumeMediaBrowser { private final MediaBrowserFactory mBrowserFactory; private final ResumeMediaBrowserLogger mLogger; private final ComponentName mComponentName; + private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback(); private MediaBrowser mMediaBrowser; + @Nullable private MediaController mMediaController; /** * Initialize a new media browser @@ -90,6 +92,7 @@ public class ResumeMediaBrowser { mComponentName, mConnectionCallback, rootHints); + updateMediaController(); mLogger.logConnection(mComponentName, "findRecentMedia"); mMediaBrowser.connect(); } @@ -154,7 +157,8 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "Service connected for " + mComponentName); - if (mMediaBrowser != null && mMediaBrowser.isConnected()) { + updateMediaController(); + if (isBrowserConnected()) { String root = mMediaBrowser.getRoot(); if (!TextUtils.isEmpty(root)) { if (mCallback != null) { @@ -207,6 +211,7 @@ public class ResumeMediaBrowser { mMediaBrowser.disconnect(); } mMediaBrowser = null; + updateMediaController(); } /** @@ -225,7 +230,8 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected()); - if (mMediaBrowser == null || !mMediaBrowser.isConnected()) { + updateMediaController(); + if (!isBrowserConnected()) { if (mCallback != null) { mCallback.onError(); } @@ -259,6 +265,7 @@ public class ResumeMediaBrowser { disconnect(); } }, rootHints); + updateMediaController(); mLogger.logConnection(mComponentName, "restart"); mMediaBrowser.connect(); } @@ -273,7 +280,7 @@ public class ResumeMediaBrowser { * @return the token, or null if the MediaBrowser is null or disconnected */ public MediaSession.Token getToken() { - if (mMediaBrowser == null || !mMediaBrowser.isConnected()) { + if (!isBrowserConnected()) { return null; } return mMediaBrowser.getSessionToken(); @@ -305,10 +312,39 @@ public class ResumeMediaBrowser { mComponentName, mConnectionCallback, rootHints); + updateMediaController(); mLogger.logConnection(mComponentName, "testConnection"); mMediaBrowser.connect(); } + /** Updates mMediaController based on our current browser values. */ + private void updateMediaController() { + MediaSession.Token controllerToken = + mMediaController != null ? mMediaController.getSessionToken() : null; + MediaSession.Token currentToken = getToken(); + boolean areEqual = (controllerToken == null && currentToken == null) + || (controllerToken != null && controllerToken.equals(currentToken)); + if (areEqual) { + return; + } + + // Whenever the token changes, un-register the callback on the old controller (if we have + // one) and create a new controller with the callback attached. + if (mMediaController != null) { + mMediaController.unregisterCallback(mMediaControllerCallback); + } + if (currentToken != null) { + mMediaController = createMediaController(currentToken); + mMediaController.registerCallback(mMediaControllerCallback); + } else { + mMediaController = null; + } + } + + private boolean isBrowserConnected() { + return mMediaBrowser != null && mMediaBrowser.isConnected(); + } + /** * Interface to handle results from ResumeMediaBrowser */ @@ -335,4 +371,12 @@ public class ResumeMediaBrowser { ResumeMediaBrowser browser) { } } + + private class SessionDestroyCallback extends MediaController.Callback { + @Override + public void onSessionDestroyed() { + mLogger.logSessionDestroyed(isBrowserConnected(), mComponentName); + disconnect(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt index ccc5edc1123a..41f735486c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt @@ -48,6 +48,27 @@ class ResumeMediaBrowserLogger @Inject constructor( }, { "Disconnecting browser for component $str1" } ) + + /** + * Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed] + * event. + * + * @param isBrowserConnected true if there's a currently connected + * [android.media.browse.MediaBrowser] and false otherwise. + * @param componentName the component name for the [ResumeMediaBrowser] that triggered this log. + */ + fun logSessionDestroyed( + isBrowserConnected: Boolean, + componentName: ComponentName + ) = buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = isBrowserConnected + str1 = componentName.toShortString() + }, + { "Session destroyed. Active browser = $bool1. Browser component = $str1." } + ) } private const val TAG = "MediaBrowser" diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 50a96f601443..c8f17d93bcc8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -17,8 +17,13 @@ package com.android.systemui.media import android.app.smartspace.SmartspaceAction +import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.text.TextUtils +import android.util.Log import com.android.internal.logging.InstanceId +import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME /** State of a Smartspace media recommendations view. */ data class SmartspaceMediaData( @@ -67,6 +72,32 @@ data class SmartspaceMediaData( * Returns the list of [recommendations] that have valid data. */ fun getValidRecommendations() = recommendations.filter { it.icon != null } + + /** Returns the upstream app name if available. */ + fun getAppName(context: Context): CharSequence? { + val nameFromAction = cardAction?.intent?.extras?.getString(KEY_SMARTSPACE_APP_NAME) + if (!TextUtils.isEmpty(nameFromAction)) { + return nameFromAction + } + + val packageManager = context.packageManager + packageManager.getLaunchIntentForPackage(packageName)?.let { + val launchActivity = it.resolveActivityInfo(packageManager, 0) + return launchActivity.loadLabel(packageManager) + } + + Log.w( + TAG, + "Package $packageName does not have a main launcher activity. " + + "Fallback to full app name") + return try { + val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0) + packageManager.getApplicationLabel(applicationInfo) + } catch (e: PackageManager.NameNotFoundException) { + null + } + } } const val NUM_REQUIRED_RECOMMENDATIONS = 3 +private val TAG = SmartspaceMediaData::class.simpleName!! 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 a397f32dcbbf..ec472c655b43 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -143,6 +143,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { if (mController.isTransferring()) { if (device.getState() == MediaDeviceState.STATE_CONNECTING && !mController.hasAdjustVolumeUserRestriction()) { + setUpDeviceIcon(device); mProgressBar.getIndeterminateDrawable().setColorFilter( new PorterDuffColorFilter( mController.getColorItemContent(), @@ -151,11 +152,13 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { false /* showSeekBar*/, true /* showProgressBar */, false /* showStatus */); } else { + setUpDeviceIcon(device); setSingleLineLayout(getItemTitle(device), false /* bFocused */); } } else { // Set different layout for each device if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { + setUpDeviceIcon(device); mTitleText.setAlpha(DEVICE_CONNECTED_ALPHA); mTitleIcon.setAlpha(DEVICE_CONNECTED_ALPHA); mStatusIcon.setImageDrawable( @@ -167,6 +170,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mSubTitleText.setText(R.string.media_output_dialog_connect_failed); mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); } else if (device.getState() == MediaDeviceState.STATE_GROUPING) { + setUpDeviceIcon(device); mProgressBar.getIndeterminateDrawable().setColorFilter( new PorterDuffColorFilter( mController.getColorItemContent(), @@ -176,7 +180,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { true /* showProgressBar */, false /* showStatus */); } else if (mController.getSelectedMediaDevice().size() > 1 && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) { + boolean isDeviceDeselectable = isDeviceIncluded( + mController.getDeselectableMediaDevice(), device); mTitleText.setTextColor(mController.getColorItemContent()); + mTitleIcon.setImageDrawable( + mContext.getDrawable(R.drawable.media_output_icon_volume)); + mTitleIcon.setColorFilter(mController.getColorItemContent()); setSingleLineLayout(getItemTitle(device), true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, false /* showStatus */); @@ -184,28 +193,36 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(true); - mCheckBox.setOnCheckedChangeListener( - (buttonView, isChecked) -> onGroupActionTriggered(false, device)); + mCheckBox.setOnCheckedChangeListener(isDeviceDeselectable + ? (buttonView, isChecked) -> onGroupActionTriggered(false, device) + : null); + mCheckBox.setEnabled(isDeviceDeselectable); + mCheckBox.setAlpha( + isDeviceDeselectable ? DEVICE_CONNECTED_ALPHA + : DEVICE_DISCONNECTED_ALPHA + ); setCheckBoxColor(mCheckBox, mController.getColorItemContent()); initSeekbar(device, isCurrentSeekbarInvisible); mEndTouchArea.setVisibility(View.VISIBLE); mEndTouchArea.setOnClickListener(null); - mEndTouchArea.setOnClickListener((v) -> mCheckBox.performClick()); + mEndTouchArea.setOnClickListener( + isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null); mEndTouchArea.setImportantForAccessibility( View.IMPORTANT_FOR_ACCESSIBILITY_YES); setUpContentDescriptionForView(mEndTouchArea, true, device); } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) { - mStatusIcon.setImageDrawable( - mContext.getDrawable(R.drawable.media_output_status_check)); - mStatusIcon.setColorFilter(mController.getColorItemContent()); + mTitleIcon.setImageDrawable( + mContext.getDrawable(R.drawable.media_output_icon_volume)); + mTitleIcon.setColorFilter(mController.getColorItemContent()); mTitleText.setTextColor(mController.getColorItemContent()); setSingleLineLayout(getItemTitle(device), true /* bFocused */, true /* showSeekBar */, - false /* showProgressBar */, true /* showStatus */); + false /* showProgressBar */, false /* showStatus */); initSeekbar(device, isCurrentSeekbarInvisible); setUpContentDescriptionForView(mContainerLayout, false, device); mCurrentActivePosition = position; } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { + setUpDeviceIcon(device); mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(false); @@ -218,6 +235,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { false /* showSeekBar */, false /* showProgressBar */, false /* showStatus */); } else { + setUpDeviceIcon(device); setSingleLineLayout(getItemTitle(device), false /* bFocused */); mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); } 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 5c2cc0b6af35..b407e76f1b11 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -166,15 +166,6 @@ public abstract class MediaOutputBaseAdapter extends void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { mDeviceId = device.getId(); - ThreadUtils.postOnBackgroundThread(() -> { - Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext); - ThreadUtils.postOnMainThread(() -> { - if (!TextUtils.equals(mDeviceId, device.getId())) { - return; - } - mTitleIcon.setImageIcon(icon); - }); - }); } abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin); @@ -414,5 +405,17 @@ public abstract class MediaOutputBaseAdapter extends mSeekBar.setEnabled(false); mSeekBar.setOnTouchListener((v, event) -> true); } + + protected void setUpDeviceIcon(MediaDevice device) { + ThreadUtils.postOnBackgroundThread(() -> { + Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext); + ThreadUtils.postOnMainThread(() -> { + if (!TextUtils.equals(mDeviceId, device.getId())) { + return; + } + mTitleIcon.setImageIcon(icon); + }); + }); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 8dcca3d55c28..1a727f8c3323 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -76,6 +76,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private static final String PREF_NAME = "MediaOutputDialog"; private static final String PREF_IS_LE_BROADCAST_FIRST_LAUNCH = "PrefIsLeBroadcastFirstLaunch"; private static final boolean DEBUG = true; + private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); private final RecyclerView.LayoutManager mLayoutManager; @@ -119,7 +120,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements Log.d(TAG, "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId); } - mMainThreadHandler.post(() -> startLeBroadcastDialog()); + mMainThreadHandler.post(() -> handleLeBroadcastStarted()); } @Override @@ -127,7 +128,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); } - handleLeBroadcastStartFailed(); + mMainThreadHandler.postDelayed(() -> handleLeBroadcastStartFailed(), + HANDLE_BROADCAST_FAILED_DELAY); } @Override @@ -137,7 +139,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId + ", metadata = " + metadata); } - mMainThreadHandler.post(() -> refresh()); + mMainThreadHandler.post(() -> handleLeBroadcastMetadataChanged()); } @Override @@ -146,7 +148,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements Log.d(TAG, "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId); } - mMainThreadHandler.post(() -> refresh()); + mMainThreadHandler.post(() -> handleLeBroadcastStopped()); } @Override @@ -154,7 +156,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason); } - mMainThreadHandler.post(() -> refresh()); + mMainThreadHandler.post(() -> handleLeBroadcastStopFailed()); } @Override @@ -163,7 +165,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements Log.d(TAG, "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId); } - mMainThreadHandler.post(() -> refresh()); + mMainThreadHandler.post(() -> handleLeBroadcastUpdated()); } @Override @@ -172,7 +174,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements Log.d(TAG, "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = " + broadcastId); } - mMainThreadHandler.post(() -> refresh()); + mMainThreadHandler.post(() -> handleLeBroadcastUpdateFailed()); } @Override @@ -384,10 +386,34 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements Bitmap.createScaledBitmap(bitmap, size, size, false)); } - protected void handleLeBroadcastStartFailed() { + public void handleLeBroadcastStarted() { + startLeBroadcastDialog(); + } + + public void handleLeBroadcastStartFailed() { mStopButton.setText(R.string.media_output_broadcast_start_failed); mStopButton.setEnabled(false); - mMainThreadHandler.postDelayed(() -> refresh(), 3000); + refresh(); + } + + public void handleLeBroadcastMetadataChanged() { + refresh(); + } + + public void handleLeBroadcastStopped() { + refresh(); + } + + public void handleLeBroadcastStopFailed() { + refresh(); + } + + public void handleLeBroadcastUpdated() { + refresh(); + } + + public void handleLeBroadcastUpdateFailed() { + refresh(); } protected void startLeBroadcast() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index dd4f1d6c9015..8f065461c22d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -61,6 +61,10 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { private ImageView mBroadcastCodeEdit; private AlertDialog mAlertDialog; private TextView mBroadcastErrorMessage; + private int mRetryCount = 0; + private String mCurrentBroadcastName; + private String mCurrentBroadcastCode; + private boolean mIsStopbyUpdateBroadcastCode = false; static final int METADATA_BROADCAST_NAME = 0; static final int METADATA_BROADCAST_CODE = 1; @@ -144,8 +148,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { //init UI component mBroadcastQrCodeView = getDialogView().requireViewById(R.id.qrcode_view); - //Set the QR code view - setQrCodeView(); mBroadcastNotify = getDialogView().requireViewById(R.id.broadcast_info); mBroadcastNotify.setOnClickListener(v -> { @@ -171,8 +173,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { launchBroadcastUpdatedDialog(true, mBroadcastCode.getText().toString()); }); - mBroadcastName.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_NAME)); - mBroadcastCode.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_CODE)); + refreshUi(); + } + + private void refreshUi() { + setQrCodeView(); + + mCurrentBroadcastName = getBroadcastMetadataInfo(METADATA_BROADCAST_NAME); + mCurrentBroadcastCode = getBroadcastMetadataInfo(METADATA_BROADCAST_CODE); + mBroadcastName.setText(mCurrentBroadcastName); + mBroadcastCode.setText(mCurrentBroadcastCode); } private void inflateBroadcastInfoArea() { @@ -239,52 +249,99 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } if (isBroadcastCode) { - handleBroadcastCodeUpdated(updatedString); + /* If the user wants to update the Broadcast Code, the Broadcast session should be + * stopped then used the new Broadcast code to start the Broadcast. + */ + mIsStopbyUpdateBroadcastCode = true; + mMediaOutputController.setBroadcastCode(updatedString); + if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + handleLeBroadcastStopFailed(); + return; + } } else { - handleBroadcastNameUpdated(updatedString); + /* If the user wants to update the Broadcast Name, we don't need to stop the Broadcast + * session. Only use the new Broadcast name to update the broadcast session. + */ + mMediaOutputController.setBroadcastName(updatedString); + if (!mMediaOutputController.updateBluetoothLeBroadcast()) { + handleLeBroadcastUpdateFailed(); + } } } - private void handleBroadcastNameUpdated(String name) { - // TODO(b/230473995) Add the retry mechanism and error handling when update fails - String currentName = mMediaOutputController.getBroadcastName(); - int retryCount = MAX_BROADCAST_INFO_UPDATE; - mMediaOutputController.setBroadcastName(name); - if (!mMediaOutputController.updateBluetoothLeBroadcast()) { - mMediaOutputController.setBroadcastName(currentName); - handleLeUpdateBroadcastFailed(retryCount); + @Override + public void handleLeBroadcastStarted() { + mRetryCount = 0; + if (mAlertDialog != null) { + mAlertDialog.dismiss(); } + refreshUi(); } - private void handleBroadcastCodeUpdated(String newPassword) { - // TODO(b/230473995) Add the retry mechanism and error handling when update fails - String currentPassword = mMediaOutputController.getBroadcastCode(); - int retryCount = MAX_BROADCAST_INFO_UPDATE; - if (!mMediaOutputController.stopBluetoothLeBroadcast()) { - mMediaOutputController.setBroadcastCode(currentPassword); - handleLeUpdateBroadcastFailed(retryCount); - return; + @Override + public void handleLeBroadcastStartFailed() { + mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode); + mRetryCount++; + + handleUpdateFailedUi(); + } + + @Override + public void handleLeBroadcastMetadataChanged() { + refreshUi(); + } + + @Override + public void handleLeBroadcastUpdated() { + mRetryCount = 0; + if (mAlertDialog != null) { + mAlertDialog.dismiss(); } + refreshUi(); + } - mMediaOutputController.setBroadcastCode(newPassword); - if (!mMediaOutputController.startBluetoothLeBroadcast()) { - mMediaOutputController.setBroadcastCode(currentPassword); - handleLeUpdateBroadcastFailed(retryCount); - return; + @Override + public void handleLeBroadcastUpdateFailed() { + //Change the value in shared preferences back to it original value + mMediaOutputController.setBroadcastName(mCurrentBroadcastName); + mRetryCount++; + + handleUpdateFailedUi(); + } + + @Override + public void handleLeBroadcastStopped() { + if (mIsStopbyUpdateBroadcastCode) { + mIsStopbyUpdateBroadcastCode = false; + mRetryCount = 0; + if (!mMediaOutputController.startBluetoothLeBroadcast()) { + handleLeBroadcastStartFailed(); + return; + } + } else { + dismiss(); } + } + + @Override + public void handleLeBroadcastStopFailed() { + //Change the value in shared preferences back to it original value + mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode); + mRetryCount++; - mAlertDialog.dismiss(); + handleUpdateFailedUi(); } - private void handleLeUpdateBroadcastFailed(int retryCount) { + private void handleUpdateFailedUi() { final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); mBroadcastErrorMessage.setVisibility(View.VISIBLE); - if (retryCount < MAX_BROADCAST_INFO_UPDATE) { + if (mRetryCount < MAX_BROADCAST_INFO_UPDATE) { if (positiveBtn != null) { positiveBtn.setEnabled(true); } mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error); } else { + mRetryCount = 0; mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md index 114589119cea..6379960b85e9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md @@ -1,11 +1,43 @@ # Media Tap-To-Transfer +## Overview This package (and child packages) include code for the media tap-to-transfer feature, which allows users to easily transfer playing media between devices. -In media transfer, there are two devices: the *sender* and the *receiver*. The sender device will -start and stop media casts to the receiver device. On both devices, System UI will display a chip -informing the user about the media cast occurring. +In media transfer, there are two devices: the **sender** and the **receiver**. The sender device +will start and stop media casts to the receiver device. On both devices, System UI will display a +chip informing the user about the media cast occurring. -This package is structured so that the sender code is in the sender package, the receiver code is -in the receiver package, and code that's shared between them is in the common package. +**Important**: System UI is **not responsible** for performing the media transfer. System UI +**only** displays an informational chip; external clients are responsible for performing the media +transfer and informing System UI about the transfer status. + +## Information flow +External clients notify System UI about the transfer status by calling `@SystemApi`s in +`StatusBarManager`. For the sender device, use the `updateMediaTapToTransferSenderDisplay` API; for +the receiver, use the `updateMediaTapToTransferReceiverDisplay` API. The APIs eventually flow into +SystemUI's `CommandQueue`, which then notifies callbacks about the new state. +`MediaTttChipControllerSender` implements the sender callback, and `MediaTttChipControllerReceiver` +implements the receiver callback. These controllers will then show or hide the tap-to-transfer chip +(depending on what information was sent in the API). + +## Architecture +This package is structured so that the sender code is in the `sender` package, the receiver code is +in the `receiver` package, and code that's shared between them is in the `common` package. + +* The `ChipStateSender` and `ChipStateReceiver` classes are enums that describe all the possible + transfer states (transfer started, transfer succeeded, etc.) and include relevant parameters for + each state. +* The `ChipSenderInfo` and `ChipReceiverInfo` classes are simple data classes that contain all the + information needed to display a chip. They include the transfer state, information about the media + being transferred, etc. +* The `MediaTttChipControllerSender` and `MediaTttChipControllerReceiver` classes are responsible + for showing or hiding the chip and updating the chip view based on information from the + `ChipInfo`. `MediaTttChipControllerCommon` has all the common logic for adding and removing the + view to the window and also includes any display logic that can be shared between the sender and + receiver. The sender and receiver controller subclasses have the display logic that's specific to + just the sender or just the receiver. + +## Testing +If you want to test out the tap-to-transfer chip without using the `@SystemApi`s, you can use adb +commands instead. Refer to `MediaTttCommandLineHelper` for information about adb commands. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 3c373f447c34..aa38b781229c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -56,6 +56,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE; import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions; +import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay; import android.annotation.IdRes; import android.app.ActivityTaskManager; @@ -68,6 +69,7 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; @@ -115,6 +117,7 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; @@ -130,6 +133,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButton; import com.android.systemui.shared.rotation.RotationButtonController; @@ -159,6 +163,7 @@ import java.io.PrintWriter; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -212,6 +217,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final NotificationShadeDepthController mNotificationShadeDepthController; private final UserContextProvider mUserContextProvider; private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener; + private final RegionSamplingHelper mRegionSamplingHelper; + private final int mNavColorSampleMargin; private NavigationBarFrame mFrame; private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -273,6 +280,16 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private boolean mShowOrientedHandleForImmersiveMode; private final DeadZone mDeadZone; private boolean mImeVisible; + private final Rect mSamplingBounds = new Rect(); + + /** + * When quickswitching between apps of different orientations, we draw a secondary home handle + * in the position of the first app's orientation. This rect represents the region of that + * home handle so we can apply the correct light/dark luma on that. + * @see {@link NavigationBar#mOrientationHandle} + */ + @android.annotation.Nullable + private Rect mOrientedHandleSamplingRegion; @com.android.internal.annotations.VisibleForTesting public enum NavBarActionEvent implements UiEventLogger.UiEventEnum { @@ -483,7 +500,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements return; } mHasBlurs = hasBlurs; - mView.setWindowHasBlurs(hasBlurs); + mRegionSamplingHelper.setWindowHasBlurs(hasBlurs); } }; @@ -512,6 +529,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements NotificationRemoteInputManager notificationRemoteInputManager, NotificationShadeDepthController notificationShadeDepthController, @Main Handler mainHandler, + @Main Executor mainExecutor, + @Background Executor bgExecutor, UiEventLogger uiEventLogger, NavBarHelper navBarHelper, LightBarController mainLightBarController, @@ -564,6 +583,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mInputMethodManager = inputMethodManager; mUserContextProvider = userContextProvider; + mNavColorSampleMargin = getResources() + .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); + mOnComputeInternalInsetsListener = info -> { // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully // gestural mode, the entire nav bar should be touchable. @@ -586,6 +608,29 @@ public class NavigationBar extends ViewController<NavigationBarView> implements false /* inScreen */, false /* useNearestRegion */)); }; + mRegionSamplingHelper = new RegionSamplingHelper(mView, + new RegionSamplingHelper.SamplingCallback() { + @Override + public void onRegionDarknessChanged(boolean isRegionDark) { + getBarTransitions().getLightTransitionsController().setIconsDark( + !isRegionDark, true /* animate */); + } + + @Override + public Rect getSampledRegion(View sampledView) { + if (mOrientedHandleSamplingRegion != null) { + return mOrientedHandleSamplingRegion; + } + + return calculateSamplingRect(); + } + + @Override + public boolean isSamplingEnabled() { + return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode); + } + }, mainExecutor, bgExecutor); + mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler); mNavBarMode = mNavigationModeController.addListener(mModeChangedListener); } @@ -600,8 +645,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements // It should also has corresponding cleanup in onViewDetached. mView.setBarTransitions(mNavigationBarTransitions); mView.setTouchHandler(mTouchHandler); - mView.setNavBarMode(mNavBarMode); + setNavBarMode(mNavBarMode); mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates); + mNavigationBarTransitions.addListener(this::onBarTransition); mView.updateRotationButton(); mView.setVisibility( @@ -674,9 +720,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements getBarTransitions().getLightTransitionsController().restoreState(mSavedState); } setNavigationIconHints(mNavigationIconHints); - mView.setWindowVisible(isNavBarWindowVisible()); + setWindowVisible(isNavBarWindowVisible()); mView.setBehavior(mBehavior); - mView.setNavBarMode(mNavBarMode); + setNavBarMode(mNavBarMode); mView.setUpdateActiveTouchRegionsCallback( () -> mOverviewProxyService.onActiveNavBarRegionChanges( getButtonLocations( @@ -838,7 +884,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true); Rect boundsRounded = new Rect(); boundsOnScreen.roundOut(boundsRounded); - mView.setOrientedHandleSamplingRegion(boundsRounded); + setOrientedHandleSamplingRegion(boundsRounded); }; mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); @@ -899,7 +945,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mOrientationHandle.setVisibility(View.GONE); } mView.setVisibility(View.VISIBLE); - mView.setOrientedHandleSamplingRegion(null); + setOrientedHandleSamplingRegion(null); } private void reconfigureHomeLongClick() { @@ -937,7 +983,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements pw.println(" mTransientShownFromGestureOnSystemBar=" + mTransientShownFromGestureOnSystemBar); dumpBarTransitions(pw, "mNavigationBarView", getBarTransitions()); + + pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); mView.dump(pw); + mRegionSamplingHelper.dump(pw); } // ----- CommandQueue Callbacks ----- @@ -973,7 +1022,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements orientSecondaryHomeHandle(); } if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state)); - mView.setWindowVisible(isNavBarWindowVisible()); + setWindowVisible(isNavBarWindowVisible()); } } @@ -1474,6 +1523,11 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } } + private void setWindowVisible(boolean visible) { + mRegionSamplingHelper.setWindowVisible(visible); + mView.setWindowVisible(visible); + } + /** Sets {@link AutoHideController} to the navigation bar. */ private void setAutoHideController(AutoHideController autoHideController) { mAutoHideController = autoHideController; @@ -1641,7 +1695,15 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (Intent.ACTION_SCREEN_OFF.equals(action) || Intent.ACTION_SCREEN_ON.equals(action)) { notifyNavigationBarScreenOn(); - mView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action)); + boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(action); + mView.onScreenStateChanged(isScreenOn); + if (isScreenOn) { + if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { + mRegionSamplingHelper.start(mSamplingBounds); + } + } else { + mRegionSamplingHelper.stop(); + } } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user @@ -1750,6 +1812,60 @@ public class NavigationBar extends ViewController<NavigationBarView> implements region.op(bounds, Region.Op.UNION); } + void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) { + mOrientedHandleSamplingRegion = orientedHandleSamplingRegion; + mRegionSamplingHelper.updateSamplingRect(); + } + + private Rect calculateSamplingRect() { + mSamplingBounds.setEmpty(); + // TODO: Extend this to 2/3 button layout as well + View view = mView.getHomeHandle().getCurrentView(); + + if (view != null) { + int[] pos = new int[2]; + view.getLocationOnScreen(pos); + Point displaySize = new Point(); + view.getContext().getDisplay().getRealSize(displaySize); + final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin, + displaySize.y - mView.getNavBarHeight(), + pos[0] + view.getWidth() + mNavColorSampleMargin, + displaySize.y); + mSamplingBounds.set(samplingBounds); + } + + return mSamplingBounds; + } + + void setNavigationBarLumaSamplingEnabled(boolean enable) { + if (enable) { + mRegionSamplingHelper.start(mSamplingBounds); + } else { + mRegionSamplingHelper.stop(); + } + } + + private void setNavBarMode(int mode) { + mView.setNavBarMode(mode, mNavigationModeController.getImeDrawsImeNavBar()); + if (isGesturalMode(mode)) { + mRegionSamplingHelper.start(mSamplingBounds); + } else { + mRegionSamplingHelper.stop(); + } + } + + void onBarTransition(int newMode) { + if (newMode == MODE_OPAQUE) { + // If the nav bar background is opaque, stop auto tinting since we know the icons are + // showing over a dark background + mRegionSamplingHelper.stop(); + getBarTransitions().getLightTransitionsController().setIconsDark( + false /* dark */, true /* animate */); + } else { + mRegionSamplingHelper.start(mSamplingBounds); + } + } + private final ModeChangedListener mModeChangedListener = new ModeChangedListener() { @Override public void onNavigationModeChanged(int mode) { @@ -1766,10 +1882,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (!canShowSecondaryHandle()) { resetSecondaryHandle(); } - if (mView != null) { - mView.setNavBarMode(mode); - mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI()); - } + setNavBarMode(mode); + mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI()); } }; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 15182c1100ec..d756af77d2a9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -244,9 +244,9 @@ public class NavigationBarController implements @Override public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { - final NavigationBarView navigationBarView = getNavigationBarView(displayId); - if (navigationBarView != null) { - navigationBarView.setNavigationBarLumaSamplingEnabled(enable); + final NavigationBar navigationBar = getNavigationBar(displayId); + if (navigationBar != null) { + navigationBar.setNavigationBarLumaSamplingEnabled(enable); } } @@ -404,10 +404,14 @@ public class NavigationBarController implements * {@code null} if no navigation bar on that display. */ public @Nullable NavigationBarView getNavigationBarView(int displayId) { - NavigationBar navBar = mNavigationBars.get(displayId); + NavigationBar navBar = getNavigationBar(displayId); return (navBar == null) ? null : navBar.getView(); } + private @Nullable NavigationBar getNavigationBar(int displayId) { + return mNavigationBars.get(displayId); + } + public void showPinningEnterExitToast(int displayId, boolean entering) { final NavigationBarView navBarView = getNavigationBarView(displayId); if (navBarView != null) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java index 4d9175b8db68..59bb2278edfe 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java @@ -168,6 +168,7 @@ public class NavigationBarInflaterView extends FrameLayout public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) { mButtonDispatchers = buttonDispatchers; + clearDispatcherViews(); for (int i = 0; i < buttonDispatchers.size(); i++) { initiallyFill(buttonDispatchers.valueAt(i)); } @@ -454,12 +455,16 @@ public class NavigationBarInflaterView extends FrameLayout } } - private void clearViews() { + private void clearDispatcherViews() { if (mButtonDispatchers != null) { for (int i = 0; i < mButtonDispatchers.size(); i++) { mButtonDispatchers.valueAt(i).clear(); } } + } + + private void clearViews() { + clearDispatcherViews(); clearAllChildren(mHorizontal.findViewById(R.id.nav_buttons)); clearAllChildren(mVertical.findViewById(R.id.nav_buttons)); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java index 11a4b3bd9274..6793f0163c43 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java @@ -48,6 +48,7 @@ public final class NavigationBarTransitions extends BarTransitions implements public static final int MIN_COLOR_ADAPT_TRANSITION_TIME = 400; public static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700; + private List<Listener> mListeners = new ArrayList<>(); /** * Notified when the color of nav bar elements changes. @@ -162,7 +163,9 @@ public final class NavigationBarTransitions extends BarTransitions implements protected void onTransition(int oldMode, int newMode, boolean animate) { super.onTransition(oldMode, newMode, animate); applyLightsOut(animate, false /*force*/); - mView.onBarTransition(newMode); + for (Listener listener : mListeners) { + listener.onTransition(newMode); + } } private void applyLightsOut(boolean animate, boolean force) { @@ -255,4 +258,16 @@ public final class NavigationBarTransitions extends BarTransitions implements pw.println(" bg color: " + mBarBackground.getColor()); pw.println(" bg frame: " + mBarBackground.getFrame()); } + + void addListener(Listener listener) { + mListeners.add(listener); + } + + void removeListener(Listener listener) { + mListeners.remove(listener); + } + + interface Listener { + void onTransition(int newMode); + } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index a13c199df41e..3fc9afe6ea94 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -24,8 +24,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_O import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay; import android.animation.LayoutTransition; import android.animation.LayoutTransition.TransitionListener; @@ -34,7 +32,6 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; -import android.annotation.Nullable; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; @@ -58,9 +55,10 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -74,7 +72,6 @@ import com.android.systemui.navigationbar.buttons.NearestTouchFrame; import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.Recents; -import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.rotation.FloatingRotationButton; import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.rotation.RotationButtonController; @@ -91,7 +88,6 @@ import com.android.wm.shell.pip.Pip; import java.io.PrintWriter; import java.util.Map; import java.util.Optional; -import java.util.concurrent.Executor; import java.util.function.Consumer; /** */ @@ -100,8 +96,6 @@ public class NavigationBarView extends FrameLayout { final static String TAG = "NavBarView"; final static boolean ALTERNATE_CAR_MODE_UI = false; - private final RegionSamplingHelper mRegionSamplingHelper; - private final int mNavColorSampleMargin; // The current view is one of mHorizontal or mVertical depending on the current configuration View mCurrentView = null; @@ -161,15 +155,6 @@ public class NavigationBarView extends FrameLayout { * fully locked mode we only show that unlocking is blocked. */ private ScreenPinningNotify mScreenPinningNotify; - private Rect mSamplingBounds = new Rect(); - /** - * When quickswitching between apps of different orientations, we draw a secondary home handle - * in the position of the first app's orientation. This rect represents the region of that - * home handle so we can apply the correct light/dark luma on that. - * @see {@link NavigationBar#mOrientationHandle} - */ - @Nullable - private Rect mOrientedHandleSamplingRegion; /** * {@code true} if the IME can render the back button and the IME switcher button. @@ -289,7 +274,6 @@ public class NavigationBarView extends FrameLayout { mDarkIconColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor); mIsVertical = false; mLongClickableAccessibilityButton = false; - mImeDrawsImeNavBar = Dependency.get(NavigationModeController.class).getImeDrawsImeNavBar(); // Set up the context group of buttons mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container); @@ -317,7 +301,7 @@ public class NavigationBarView extends FrameLayout { R.drawable.ic_sysbar_rotate_button_ccw_start_90, R.drawable.ic_sysbar_rotate_button_cw_start_0, R.drawable.ic_sysbar_rotate_button_cw_start_90, - () -> getDisplay().getRotation()); + () -> mCurrentRotation); mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); @@ -333,33 +317,6 @@ public class NavigationBarView extends FrameLayout { mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton); mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup); mDeadZone = new DeadZone(this); - - mNavColorSampleMargin = getResources() - .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); - Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR); - mRegionSamplingHelper = new RegionSamplingHelper(this, - new RegionSamplingHelper.SamplingCallback() { - @Override - public void onRegionDarknessChanged(boolean isRegionDark) { - getLightTransitionsController().setIconsDark(!isRegionDark , - true /* animate */); - } - - @Override - public Rect getSampledRegion(View sampledView) { - if (mOrientedHandleSamplingRegion != null) { - return mOrientedHandleSamplingRegion; - } - - updateSamplingRect(); - return mSamplingBounds; - } - - @Override - public boolean isSamplingEnabled() { - return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode); - } - }, backgroundExecutor); } public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) { @@ -407,28 +364,6 @@ public class NavigationBarView extends FrameLayout { return super.onTouchEvent(event); } - /** - * If we're blurring the shade window. - */ - public void setWindowHasBlurs(boolean hasBlurs) { - mRegionSamplingHelper.setWindowHasBlurs(hasBlurs); - } - - void onTransientStateChanged(boolean isTransient, boolean isGestureOnSystemBar) { - mEdgeBackGestureHandler.onNavBarTransientStateChanged(isTransient); - } - - void onBarTransition(int newMode) { - if (newMode == MODE_OPAQUE) { - // If the nav bar background is opaque, stop auto tinting since we know the icons are - // showing over a dark background - mRegionSamplingHelper.stop(); - getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */); - } else { - mRegionSamplingHelper.start(mSamplingBounds); - } - } - public void abortCurrentGesture() { getHomeButton().abortCurrentGesture(); } @@ -538,6 +473,7 @@ public class NavigationBarView extends FrameLayout { mRotationButtonController.setRotationButton(mRotationContextButton, mRotationButtonListener); } + mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); } public KeyButtonDrawable getBackDrawable() { @@ -603,17 +539,9 @@ public class NavigationBarView extends FrameLayout { /** To be called when screen lock/unlock state changes */ public void onScreenStateChanged(boolean isScreenOn) { mScreenOn = isScreenOn; - if (isScreenOn) { - if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { - mRegionSamplingHelper.start(mSamplingBounds); - } - } else { - mRegionSamplingHelper.stop(); - } } public void setWindowVisible(boolean visible) { - mRegionSamplingHelper.setWindowVisible(visible); mRotationButtonController.onNavigationBarWindowVisibilityChange(visible); } @@ -879,18 +807,12 @@ public class NavigationBarView extends FrameLayout { wm.updateViewLayout(navbarView, lp); } - void setNavBarMode(int mode) { + void setNavBarMode(int mode, boolean imeDrawsImeNavBar) { mNavBarMode = mode; - mImeDrawsImeNavBar = Dependency.get(NavigationModeController.class).getImeDrawsImeNavBar(); + mImeDrawsImeNavBar = imeDrawsImeNavBar; mBarTransitions.onNavigationModeChanged(mNavBarMode); mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode); updateRotationButton(); - - if (isGesturalMode(mNavBarMode)) { - mRegionSamplingHelper.start(mSamplingBounds); - } else { - mRegionSamplingHelper.stop(); - } } public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) { @@ -915,29 +837,6 @@ public class NavigationBarView extends FrameLayout { super.onDraw(canvas); } - private void updateSamplingRect() { - mSamplingBounds.setEmpty(); - // TODO: Extend this to 2/3 button layout as well - View view = getHomeHandle().getCurrentView(); - - if (view != null) { - int[] pos = new int[2]; - view.getLocationOnScreen(pos); - Point displaySize = new Point(); - view.getContext().getDisplay().getRealSize(displaySize); - final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin, - displaySize.y - getNavBarHeight(), - pos[0] + view.getWidth() + mNavColorSampleMargin, - displaySize.y); - mSamplingBounds.set(samplingBounds); - } - } - - void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) { - mOrientedHandleSamplingRegion = orientedHandleSamplingRegion; - mRegionSamplingHelper.updateSamplingRect(); - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -978,15 +877,27 @@ public class NavigationBarView extends FrameLayout { return mCurrentRotation != rotation; } + private void updateCurrentRotation() { + final int rotation = mConfiguration.windowConfiguration.getDisplayRotation(); + if (mCurrentRotation == rotation) { + return; + } + mCurrentRotation = rotation; + mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90); + mDeadZone.onConfigurationChanged(mCurrentRotation); + if (DEBUG) { + Log.d(TAG, "updateCurrentRotation(): rot=" + mCurrentRotation); + } + } + private void updateCurrentView() { resetViews(); mCurrentView = mIsVertical ? mVertical : mHorizontal; mCurrentView.setVisibility(View.VISIBLE); mNavigationInflaterView.setVertical(mIsVertical); - mCurrentRotation = getContextDisplay().getRotation(); - mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90); mNavigationInflaterView.updateButtonDispatchersCurrentView(); updateLayoutTransitionsEnabled(); + updateCurrentRotation(); } private void resetViews() { @@ -1019,17 +930,11 @@ public class NavigationBarView extends FrameLayout { public void reorient() { updateCurrentView(); - ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone); - mDeadZone.onConfigurationChanged(mCurrentRotation); // force the low profile & disabled states into compliance mBarTransitions.init(); - if (DEBUG) { - Log.d(TAG, "reorient(): rot=" + mCurrentRotation); - } - // Resolve layout direction if not resolved since components changing layout direction such // as changing languages will recreate this view and the direction will be resolved later if (!isLayoutDirectionResolved()) { @@ -1076,7 +981,7 @@ public class NavigationBarView extends FrameLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - private int getNavBarHeight() { + int getNavBarHeight() { return mIsVertical ? getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_height_landscape) @@ -1100,6 +1005,7 @@ public class NavigationBarView extends FrameLayout { boolean uiCarModeChanged = updateCarMode(); updateIcons(mTmpLastConfiguration); updateRecentsIcon(); + updateCurrentRotation(); mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration); if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) { @@ -1184,7 +1090,7 @@ public class NavigationBarView extends FrameLayout { mEdgeBackGestureHandler.onNavBarDetached(); } - public void dump(PrintWriter pw) { + void dump(PrintWriter pw) { final Rect r = new Rect(); final Point size = new Point(); getContextDisplay().getRealSize(size); @@ -1211,7 +1117,6 @@ public class NavigationBarView extends FrameLayout { mIsVertical ? "true" : "false", getLightTransitionsController().getCurrentDarkIntensity())); - pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); pw.println(" mScreenOn: " + mScreenOn); @@ -1228,7 +1133,6 @@ public class NavigationBarView extends FrameLayout { } mBarTransitions.dump(pw); mContextualButtonGroup.dump(pw); - mRegionSamplingHelper.dump(pw); mEdgeBackGestureHandler.dump(pw); } @@ -1289,13 +1193,6 @@ public class NavigationBarView extends FrameLayout { mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds); }); - void setNavigationBarLumaSamplingEnabled(boolean enable) { - if (enable) { - mRegionSamplingHelper.start(mSamplingBounds); - } else { - mRegionSamplingHelper.stop(); - } - } interface UpdateActiveTouchRegionsCallback { void update(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index d27b71673ce5..622f5a279a5f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -273,9 +273,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { mLongClicked = false; setPressed(true); - // Use raw X and Y to detect gestures in case a parent changes the x and y values - mTouchDownX = (int) ev.getRawX(); - mTouchDownY = (int) ev.getRawY(); + mTouchDownX = (int) ev.getX(); + mTouchDownY = (int) ev.getY(); if (mCode != KEYCODE_UNKNOWN) { sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); } else { @@ -289,8 +288,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); break; case MotionEvent.ACTION_MOVE: - x = (int)ev.getRawX(); - y = (int)ev.getRawY(); + x = (int) ev.getX(); + y = (int) ev.getY(); float slop = QuickStepContract.getQuickStepTouchSlopPx(getContext()); if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 001a4628916f..ea41fe74f798 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -105,8 +105,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; private static final int MAX_NUM_LOGGED_GESTURES = 10; - // Temporary log until b/202433017 is resolved - static final boolean DEBUG_MISSING_GESTURE = true; + static final boolean DEBUG_MISSING_GESTURE = false; static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; private ISystemGestureExclusionListener mGestureExclusionListener = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index e5dc0ec54676..a74c59618c95 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -363,6 +363,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl initializeBackAnimation(); setVisibility(GONE); + Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR); boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY; mRegionSamplingHelper = new RegionSamplingHelper(this, diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index dbdb9d268aab..cd6eb99e259e 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -58,7 +58,8 @@ class PrivacyItemController @Inject constructor( internal companion object { val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA, AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO, - AppOpsManager.OP_PHONE_CALL_MICROPHONE) + AppOpsManager.OP_PHONE_CALL_MICROPHONE, + AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) val OPS_LOCATION = intArrayOf( AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION) @@ -315,6 +316,7 @@ class PrivacyItemController @Inject constructor( AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION AppOpsManager.OP_PHONE_CALL_MICROPHONE, + AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE else -> return null } diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index 8000bdccfa68..2c20feb19342 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -29,7 +29,6 @@ import android.provider.Settings; import android.util.Log; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.settings.UserTracker; @@ -119,7 +118,6 @@ public class QRCodeScannerController implements mSecureSettings = secureSettings; mDeviceConfigProxy = proxy; mUserTracker = userTracker; - mConfigEnableLockScreenButton = mContext.getResources().getBoolean( android.R.bool.config_enableQrCodeScannerOnLockScreen); } @@ -258,16 +256,20 @@ public class QRCodeScannerController implements } } + private String getDefaultScannerActivity() { + return mContext.getResources().getString( + com.android.internal.R.string.config_defaultQrCodeComponent); + } + private void updateQRCodeScannerActivityDetails() { String qrCodeScannerActivity = mDeviceConfigProxy.getString( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, ""); // "" means either the flags is not available or is set to "", and in both the cases we - // want to use R.string.def_qr_code_component + // want to use R.string.config_defaultQrCodeComponent if (Objects.equals(qrCodeScannerActivity, "")) { - qrCodeScannerActivity = - mContext.getResources().getString(R.string.def_qr_code_component); + qrCodeScannerActivity = getDefaultScannerActivity(); } String prevQrCodeScannerActivity = mQRCodeScannerActivity; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index dd99db49c1d2..584de6e8c28f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -22,6 +22,9 @@ import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_CA_CERT_SUBTITLE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_NETWORK_SUBTITLE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_VPN_SUBTITLE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES; @@ -92,6 +95,7 @@ import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.util.ViewController; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Named; @@ -106,7 +110,7 @@ class QSSecurityFooter extends ViewController<View> private final TextView mFooterText; private final ImageView mPrimaryFooterIcon; - private final Context mContext; + private Context mContext; private final DevicePolicyManager mDpm; private final Callback mCallback = new Callback(); private final SecurityController mSecurityController; @@ -141,6 +145,63 @@ class QSSecurityFooter extends ViewController<View> } }; + private Supplier<String> mManagementTitleSupplier = () -> + mContext == null ? null : mContext.getString(R.string.monitoring_title_device_owned); + + private Supplier<String> mManagementMessageSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.quick_settings_disclosure_management); + + private Supplier<String> mManagementMonitoringStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.quick_settings_disclosure_management_monitoring); + + private Supplier<String> mManagementMultipleVpnStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.quick_settings_disclosure_management_vpns); + + private Supplier<String> mWorkProfileMonitoringStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.quick_settings_disclosure_managed_profile_monitoring); + + private Supplier<String> mWorkProfileNetworkStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.quick_settings_disclosure_managed_profile_network_activity); + + private Supplier<String> mMonitoringSubtitleCaCertStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_subtitle_ca_certificate); + + private Supplier<String> mMonitoringSubtitleNetworkStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_subtitle_network_logging); + + private Supplier<String> mMonitoringSubtitleVpnStringSupplier = () -> + mContext == null ? null : mContext.getString(R.string.monitoring_subtitle_vpn); + + private Supplier<String> mViewPoliciesButtonStringSupplier = () -> + mContext == null ? null : mContext.getString(R.string.monitoring_button_view_policies); + + private Supplier<String> mManagementDialogStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_description_management); + + private Supplier<String> mManagementDialogCaCertStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_description_management_ca_certificate); + + private Supplier<String> mWorkProfileDialogCaCertStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_description_managed_profile_ca_certificate); + + private Supplier<String> mManagementDialogNetworkStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_description_management_network_logging); + + private Supplier<String> mWorkProfileDialogNetworkStringSupplier = () -> + mContext == null ? null : mContext.getString( + R.string.monitoring_description_managed_profile_network_logging); + @Inject QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView, UserTracker userTracker, @Main Handler mainHandler, @@ -337,9 +398,7 @@ class QSSecurityFooter extends ViewController<View> private String getManagedDeviceMonitoringText(CharSequence organizationName) { if (organizationName == null) { return mDpm.getResources().getString( - QS_MSG_MANAGEMENT_MONITORING, - () -> mContext.getString( - R.string.quick_settings_disclosure_management_monitoring)); + QS_MSG_MANAGEMENT_MONITORING, mManagementMonitoringStringSupplier); } return mDpm.getResources().getString( QS_MSG_NAMED_MANAGEMENT_MONITORING, @@ -354,9 +413,7 @@ class QSSecurityFooter extends ViewController<View> if (vpnName != null && vpnNameWorkProfile != null) { if (organizationName == null) { return mDpm.getResources().getString( - QS_MSG_MANAGEMENT_MULTIPLE_VPNS, - () -> mContext.getString( - R.string.quick_settings_disclosure_management_vpns)); + QS_MSG_MANAGEMENT_MULTIPLE_VPNS, mManagementMultipleVpnStringSupplier); } return mDpm.getResources().getString( QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS, @@ -386,10 +443,7 @@ class QSSecurityFooter extends ViewController<View> private String getMangedDeviceGeneralText(CharSequence organizationName) { if (organizationName == null) { - return mDpm.getResources().getString( - QS_MSG_MANAGEMENT, - () -> mContext.getString( - R.string.quick_settings_disclosure_management)); + return mDpm.getResources().getString(QS_MSG_MANAGEMENT, mManagementMessageSupplier); } if (isFinancedDevice()) { return mContext.getString( @@ -431,9 +485,7 @@ class QSSecurityFooter extends ViewController<View> if (hasCACertsInWorkProfile && isWorkProfileOn) { if (workProfileOrganizationName == null) { return mDpm.getResources().getString( - QS_MSG_WORK_PROFILE_MONITORING, - () -> mContext.getString( - R.string.quick_settings_disclosure_managed_profile_monitoring)); + QS_MSG_WORK_PROFILE_MONITORING, mWorkProfileMonitoringStringSupplier); } return mDpm.getResources().getString( QS_MSG_NAMED_WORK_PROFILE_MONITORING, @@ -478,10 +530,9 @@ class QSSecurityFooter extends ViewController<View> private String getManagedProfileNetworkActivityText() { return mDpm.getResources().getString( - QS_MSG_WORK_PROFILE_NETWORK, - () -> mContext.getString( - R.string.quick_settings_disclosure_managed_profile_network_activity)); + QS_MSG_WORK_PROFILE_NETWORK, mWorkProfileNetworkStringSupplier); } + @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { @@ -569,6 +620,12 @@ class QSSecurityFooter extends ViewController<View> caCertsWarning.setText(caCertsMessage); // Make "Open trusted credentials"-link clickable caCertsWarning.setMovementMethod(new LinkMovementMethod()); + + TextView caCertsSubtitle = (TextView) dialogView.findViewById(R.id.ca_certs_subtitle); + String caCertsSubtitleMessage = mDpm.getResources().getString( + QS_DIALOG_MONITORING_CA_CERT_SUBTITLE, mMonitoringSubtitleCaCertStringSupplier); + caCertsSubtitle.setText(caCertsSubtitleMessage); + } // network logging section @@ -581,6 +638,13 @@ class QSSecurityFooter extends ViewController<View> TextView networkLoggingWarning = (TextView) dialogView.findViewById(R.id.network_logging_warning); networkLoggingWarning.setText(networkLoggingMessage); + + TextView networkLoggingSubtitle = (TextView) dialogView.findViewById( + R.id.network_logging_subtitle); + String networkLoggingSubtitleMessage = mDpm.getResources().getString( + QS_DIALOG_MONITORING_NETWORK_SUBTITLE, + mMonitoringSubtitleNetworkStringSupplier); + networkLoggingSubtitle.setText(networkLoggingSubtitleMessage); } // vpn section @@ -594,6 +658,11 @@ class QSSecurityFooter extends ViewController<View> vpnWarning.setText(vpnMessage); // Make "Open VPN Settings"-link clickable vpnWarning.setMovementMethod(new LinkMovementMethod()); + + TextView vpnSubtitle = (TextView) dialogView.findViewById(R.id.vpn_subtitle); + String vpnSubtitleMessage = mDpm.getResources().getString( + QS_DIALOG_MONITORING_VPN_SUBTITLE, mMonitoringSubtitleVpnStringSupplier); + vpnSubtitle.setText(vpnSubtitleMessage); } // Note: if a new section is added, should update configSubtitleVisibility to include @@ -657,8 +726,7 @@ class QSSecurityFooter extends ViewController<View> @VisibleForTesting String getSettingsButton() { return mDpm.getResources().getString( - QS_DIALOG_VIEW_POLICIES, - () -> mContext.getString(R.string.monitoring_button_view_policies)); + QS_DIALOG_VIEW_POLICIES, mViewPoliciesButtonStringSupplier); } private String getPositiveButton() { @@ -692,9 +760,7 @@ class QSSecurityFooter extends ViewController<View> organizationName); } } - return mDpm.getResources().getString( - QS_DIALOG_MANAGEMENT, - () -> mContext.getString(R.string.monitoring_description_management)); + return mDpm.getResources().getString(QS_DIALOG_MANAGEMENT, mManagementDialogStringSupplier); } @Nullable @@ -703,15 +769,11 @@ class QSSecurityFooter extends ViewController<View> if (!(hasCACerts || hasCACertsInWorkProfile)) return null; if (isDeviceManaged) { return mDpm.getResources().getString( - QS_DIALOG_MANAGEMENT_CA_CERT, - () -> mContext.getString( - R.string.monitoring_description_management_ca_certificate)); + QS_DIALOG_MANAGEMENT_CA_CERT, mManagementDialogCaCertStringSupplier); } if (hasCACertsInWorkProfile) { return mDpm.getResources().getString( - QS_DIALOG_WORK_PROFILE_CA_CERT, - () -> mContext.getString( - R.string.monitoring_description_managed_profile_ca_certificate)); + QS_DIALOG_WORK_PROFILE_CA_CERT, mWorkProfileDialogCaCertStringSupplier); } return mContext.getString(R.string.monitoring_description_ca_certificate); } @@ -722,14 +784,10 @@ class QSSecurityFooter extends ViewController<View> if (!isNetworkLoggingEnabled) return null; if (isDeviceManaged) { return mDpm.getResources().getString( - QS_DIALOG_MANAGEMENT_NETWORK, - () -> mContext.getString( - R.string.monitoring_description_management_network_logging)); + QS_DIALOG_MANAGEMENT_NETWORK, mManagementDialogNetworkStringSupplier); } else { return mDpm.getResources().getString( - QS_DIALOG_WORK_PROFILE_NETWORK, - () -> mContext.getString( - R.string.monitoring_description_managed_profile_network_logging)); + QS_DIALOG_WORK_PROFILE_NETWORK, mWorkProfileDialogNetworkStringSupplier); } } @@ -799,7 +857,7 @@ class QSSecurityFooter extends ViewController<View> } else { return mDpm.getResources().getString( QS_DIALOG_MANAGEMENT_TITLE, - () -> mContext.getString(R.string.monitoring_title_device_owned)); + mManagementTitleSupplier); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 311ee56477de..3d00dd49cb1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -47,7 +47,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { private int mMaxColumns = NO_MAX_COLUMNS; protected int mResourceColumns; private float mSquishinessFraction = 1f; - private int mLastTileBottom; + protected int mLastTileBottom; public TileLayout(Context context) { this(context, null); @@ -243,12 +243,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout { record.tileView.setLeftTopRightBottom(left, top, right, bottom); } record.tileView.setPosition(i); - if (forLayout) { - mLastTileBottom = record.tileView.getBottom(); - } else { - float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction); - mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale); - } + + // Set the bottom to the unoverriden squished bottom. This is to avoid fake bottoms that + // are only used for QQS -> QS expansion animations + float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction); + mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale); } } @@ -258,7 +257,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } protected int getRowTop(int row) { - return (int) (row * (mCellHeight * mSquishinessFraction + mCellMarginVertical)); + float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction); + return (int) (row * (mCellHeight * scale + mCellMarginVertical)); } protected int getColumnStart(int column) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index d7aa8b21a150..7c8e77b5d993 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -30,6 +30,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -158,7 +159,9 @@ public class HotspotTile extends QSTileImpl<BooleanState> { state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; - final boolean isTileUnavailable = isDataSaverEnabled; + final boolean isWifiTetheringAllowed = + WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mHost.getUserContext()); + final boolean isTileUnavailable = isDataSaverEnabled || !isWifiTetheringAllowed; final boolean isTileActive = (state.value || state.isTransient); if (isTileUnavailable) { @@ -167,15 +170,17 @@ public class HotspotTile extends QSTileImpl<BooleanState> { state.state = isTileActive ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } - state.secondaryLabel = getSecondaryLabel( - isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices); + state.secondaryLabel = getSecondaryLabel(isTileActive, isTransient, isDataSaverEnabled, + numConnectedDevices, isWifiTetheringAllowed); state.stateDescription = state.secondaryLabel; } @Nullable private String getSecondaryLabel(boolean isActive, boolean isTransient, - boolean isDataSaverEnabled, int numConnectedDevices) { - if (isTransient) { + boolean isDataSaverEnabled, int numConnectedDevices, boolean isWifiTetheringAllowed) { + if (!isWifiTetheringAllowed) { + return mContext.getString(R.string.wifitrackerlib_admin_restricted_network); + } else if (isTransient) { return mContext.getString(R.string.quick_settings_hotspot_secondary_label_transient); } else if (isDataSaverEnabled) { return mContext.getString( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4728c678f96c..489f881c36cf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -78,7 +78,6 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import android.window.WindowContext; @@ -484,6 +483,7 @@ public class ScreenshotController { setWindowFocusable(false); } }); + mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); mScreenshotView.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { @@ -558,14 +558,9 @@ public class ScreenshotController { private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, ComponentName topComponent, boolean showFlash) { - if (mAccessibilityManager.isEnabled()) { - AccessibilityEvent event = - new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - event.setContentDescription( - mContext.getResources().getString(R.string.screenshot_saving_title)); - mAccessibilityManager.sendAccessibilityEvent(event); - } - + withWindowAttached(() -> + mScreenshotView.announceForAccessibility( + mContext.getResources().getString(R.string.screenshot_saving_title))); if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason @@ -632,6 +627,7 @@ public class ScreenshotController { } } } + @Override public void requestCompatCameraControl(boolean showControl, boolean transformationApplied, @@ -717,6 +713,7 @@ public class ScreenshotController { Log.e(TAG, "requestScrollCapture failed", e); } } + ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture; private void runBatchScrollCapture(ScrollCaptureResponse response) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 6af6e36a75f7..48bb2af2ab8d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -18,6 +18,7 @@ package com.android.systemui.screenshot; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_TAKE_SCREENSHOT; import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; @@ -83,6 +84,7 @@ import android.widget.LinearLayout; import androidx.constraintlayout.widget.ConstraintLayout; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; @@ -149,7 +151,6 @@ public class ScreenshotView extends FrameLayout implements private ImageView mActionsContainerBackground; private HorizontalScrollView mActionsContainer; private LinearLayout mActionsView; - private ImageView mBackgroundProtection; private FrameLayout mDismissButton; private OverlayActionChip mShareChip; private OverlayActionChip mEditChip; @@ -167,6 +168,9 @@ public class ScreenshotView extends FrameLayout implements private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>(); private PendingInteraction mPendingInteraction; + private final InteractionJankMonitor mInteractionJankMonitor; + private long mDefaultTimeoutOfTimeoutHandler; + private enum PendingInteraction { PREVIEW, EDIT, @@ -190,6 +194,7 @@ public class ScreenshotView extends FrameLayout implements Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mResources = mContext.getResources(); + mInteractionJankMonitor = getInteractionJankMonitorInstance(); mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale); @@ -230,6 +235,14 @@ public class ScreenshotView extends FrameLayout implements }); } + private InteractionJankMonitor getInteractionJankMonitorInstance() { + return InteractionJankMonitor.getInstance(); + } + + void setDefaultTimeoutMillis(long timeout) { + mDefaultTimeoutOfTimeoutHandler = timeout; + } + public void hideScrollChip() { mScrollChip.setVisibility(View.GONE); } @@ -345,8 +358,6 @@ public class ScreenshotView extends FrameLayout implements R.id.actions_container_background)); mActionsContainer = requireNonNull(findViewById(R.id.actions_container)); mActionsView = requireNonNull(findViewById(R.id.screenshot_actions)); - mBackgroundProtection = requireNonNull( - findViewById(R.id.screenshot_actions_background)); mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button)); mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview)); mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash)); @@ -394,18 +405,13 @@ public class ScreenshotView extends FrameLayout implements } mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, mPackageName); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - mBackgroundProtection.animate() - .alpha(0).setDuration(animation.getDuration()).start(); - } - }); } @Override public void onDismissComplete() { + if (mInteractionJankMonitor.isInstrumenting(CUJ_TAKE_SCREENSHOT)) { + mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT); + } mCallbacks.onDismiss(); } }); @@ -606,6 +612,20 @@ public class ScreenshotView extends FrameLayout implements dropInAnimation.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationCancel(Animator animation) { + mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT); + } + + @Override + public void onAnimationStart(Animator animation) { + InteractionJankMonitor.Configuration.Builder builder = + InteractionJankMonitor.Configuration.Builder.withView( + CUJ_TAKE_SCREENSHOT, mScreenshotPreview) + .setTag("DropIn"); + mInteractionJankMonitor.begin(builder); + } + + @Override public void onAnimationEnd(Animator animation) { if (DEBUG_ANIM) { Log.d(TAG, "drop-in animation ended"); @@ -631,7 +651,7 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f); mScreenshotPreview.setY(finalPos.y - mScreenshotPreview.getHeight() / 2f); requestLayout(); - + mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT); createScreenshotActionsShadeAnimation().start(); } }); @@ -702,9 +722,30 @@ public class ScreenshotView extends FrameLayout implements mActionsContainer.setVisibility(View.VISIBLE); mActionsContainerBackground.setVisibility(View.VISIBLE); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT); + } + + @Override + public void onAnimationEnd(Animator animation) { + mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT); + } + + @Override + public void onAnimationStart(Animator animation) { + InteractionJankMonitor.Configuration.Builder builder = + InteractionJankMonitor.Configuration.Builder.withView( + CUJ_TAKE_SCREENSHOT, mScreenshotStatic) + .setTag("Actions") + .setTimeout(mDefaultTimeoutOfTimeoutHandler); + mInteractionJankMonitor.begin(builder); + } + }); + animator.addUpdateListener(animation -> { float t = animation.getAnimatedFraction(); - mBackgroundProtection.setAlpha(t); float containerAlpha = t < alphaFraction ? t / alphaFraction : 1; mActionsContainer.setAlpha(containerAlpha); mActionsContainerBackground.setAlpha(containerAlpha); @@ -910,7 +951,6 @@ public class ScreenshotView extends FrameLayout implements } mDismissButton.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); - mBackgroundProtection.setVisibility(View.GONE); // set these invisible, but not gone, so that the views are laid out correctly mActionsContainerBackground.setVisibility(View.INVISIBLE); mScreenshotPreviewBorder.setVisibility(View.INVISIBLE); @@ -932,7 +972,6 @@ public class ScreenshotView extends FrameLayout implements mDismissButton.setVisibility(View.VISIBLE); } mActionsContainer.setVisibility(View.VISIBLE); - mBackgroundProtection.setVisibility(View.VISIBLE); mActionsContainerBackground.setVisibility(View.VISIBLE); mScreenshotPreviewBorder.setVisibility(View.VISIBLE); mScreenshotPreview.setVisibility(View.VISIBLE); @@ -969,7 +1008,6 @@ public class ScreenshotView extends FrameLayout implements mPendingSharedTransition = false; mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); - mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); mScrollingScrim.setVisibility(View.GONE); mScrollablePreview.setVisibility(View.GONE); @@ -1016,7 +1054,6 @@ public class ScreenshotView extends FrameLayout implements mDismissButton.setAlpha(alpha); mActionsContainerBackground.setAlpha(alpha); mActionsContainer.setAlpha(alpha); - mBackgroundProtection.setAlpha(alpha); mScreenshotPreviewBorder.setAlpha(alpha); }); alphaAnim.setDuration(600); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 7f3758e208db..2621f6da7afa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -16,6 +16,7 @@ package com.android.systemui.screenshot; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN; import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE; @@ -54,7 +55,9 @@ import androidx.annotation.NonNull; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; +import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -70,6 +73,7 @@ public class TakeScreenshotService extends Service { private final ScreenshotNotificationsController mNotificationsController; private final Handler mHandler; private final Context mContext; + private final @Background Executor mBgExecutor; private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() { @Override @@ -97,7 +101,8 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager, DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger, - ScreenshotNotificationsController notificationsController, Context context) { + ScreenshotNotificationsController notificationsController, Context context, + @Background Executor bgExecutor) { if (DEBUG_SERVICE) { Log.d(TAG, "new " + this); } @@ -108,6 +113,7 @@ public class TakeScreenshotService extends Service { mUiEventLogger = uiEventLogger; mNotificationsController = notificationsController; mContext = context; + mBgExecutor = bgExecutor; } @Override @@ -189,12 +195,18 @@ public class TakeScreenshotService extends Service { requestCallback.reportError(); return true; } - if(mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { - Log.w(TAG, "Skipping screenshot because an IT admin has disabled " - + "screenshots on the device"); - Toast.makeText(mContext, R.string.screenshot_blocked_by_admin, - Toast.LENGTH_SHORT).show(); - requestCallback.reportError(); + + if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { + mBgExecutor.execute(() -> { + Log.w(TAG, "Skipping screenshot because an IT admin has disabled " + + "screenshots on the device"); + String blockedByAdminText = mDevicePolicyManager.getResources().getString( + SCREENSHOT_BLOCKED_BY_ADMIN, + () -> mContext.getString(R.string.screenshot_blocked_by_admin)); + mHandler.post(() -> + Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show()); + requestCallback.reportError(); + }); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java index 9156601e6638..71c2cb4a5cb9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java @@ -72,6 +72,10 @@ public class TimeoutHandler extends Handler { mDefaultTimeout = timeout; } + int getDefaultTimeoutMillis() { + return mDefaultTimeout; + } + /** * Cancel the current timeout, if any. To reset the delayed runnable use resetTimeout instead. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java index 4d933d9ad21e..e44d334c776d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java @@ -29,7 +29,6 @@ import com.android.keyguard.AlphaOptimizedLinearLayout; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener; import java.util.ArrayList; @@ -49,6 +48,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { private TextView mTextView; private NotificationEntry mShowingEntry; private Runnable mOnDrawingRectChangedListener; + private boolean mRedactSensitiveContent; public HeadsUpStatusBarView(Context context) { this(context, null); @@ -111,29 +111,28 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { } public void setEntry(NotificationEntry entry) { - if (mShowingEntry != null) { - mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener); - } mShowingEntry = entry; - if (mShowingEntry != null) { CharSequence text = entry.headsUpStatusBarText; - if (entry.isSensitive()) { + if (mRedactSensitiveContent && entry.hasSensitiveContents()) { text = entry.headsUpStatusBarTextPublic; } mTextView.setText(text); - mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener); } } - private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> { - if (entry != mShowingEntry) { - throw new IllegalStateException("Got a sensitivity change for " + entry - + " but mShowingEntry is " + mShowingEntry); + public void setRedactSensitiveContent(boolean redactSensitiveContent) { + if (mRedactSensitiveContent == redactSensitiveContent) { + return; + } + mRedactSensitiveContent = redactSensitiveContent; + if (mShowingEntry != null && mShowingEntry.hasSensitiveContents()) { + mTextView.setText( + mRedactSensitiveContent + ? mShowingEntry.headsUpStatusBarTextPublic + : mShowingEntry.headsUpStatusBarText); } - // Update the text - setEntry(entry); - }; + } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index c1ea6bf7cec8..cbe722b6f82f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -386,7 +386,7 @@ class LockscreenShadeTransitionController @Inject constructor( } if (view is ExpandableNotificationRow) { // Only drag down on sensitive views, otherwise the ExpandHelper will take this - return view.entry.isSensitive + return lockScreenUserManager.notifNeedsRedactionInPublic(view.entry) } } return false @@ -552,7 +552,8 @@ class LockscreenShadeTransitionController @Inject constructor( logger.logShadeDisabledOnGoToLockedShade() return } - var userId: Int = lockScreenUserManager.getCurrentUserId() + val currentUser = lockScreenUserManager.currentUserId + var userId: Int = currentUser var entry: NotificationEntry? = null if (expandView is ExpandableNotificationRow) { entry = expandView.entry @@ -562,12 +563,18 @@ class LockscreenShadeTransitionController @Inject constructor( entry.setGroupExpansionChanging(true) userId = entry.sbn.userId } - var fullShadeNeedsBouncer = (!lockScreenUserManager.userAllowsPrivateNotificationsInPublic( - lockScreenUserManager.getCurrentUserId()) || - !lockScreenUserManager.shouldShowLockscreenNotifications() || - falsingCollector.shouldEnforceBouncer()) - if (keyguardBypassController.bypassEnabled) { - fullShadeNeedsBouncer = false + val fullShadeNeedsBouncer = when { + // No bouncer necessary if we're bypassing + keyguardBypassController.bypassEnabled -> false + // Redacted notificationss are present, bouncer should be shown before un-redacting in + // the full shade + lockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(currentUser) -> true + // Notifications are hidden in public, bouncer should be shown before showing them in + // the full shade + !lockScreenUserManager.shouldShowLockscreenNotifications() -> true + // Bouncer is being enforced, so we need to show it + falsingCollector.shouldEnforceBouncer() -> true + else -> false } if (lockScreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { statusBarStateController.setLeaveOpenOnKeyguardHide(true) @@ -911,4 +918,4 @@ class DragDownHelper( host.getLocationOnScreen(temp2) return expandCallback.getChildAtRawPosition(x + temp2[0], y + temp2[1]) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index 9a1a144924e2..5fd9671bda01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -71,17 +71,22 @@ public interface NotificationLockscreenUserManager { boolean shouldHideNotifications(String key); boolean shouldShowOnKeyguard(NotificationEntry entry); + void addOnNeedsRedactionInPublicChangedListener(Runnable listener); + + void removeOnNeedsRedactionInPublicChangedListener(Runnable listener); + boolean isAnyProfilePublicMode(); void updatePublicMode(); - boolean needsRedaction(NotificationEntry entry); + /** Does this notification require redaction if it is displayed when the device is public? */ + boolean notifNeedsRedactionInPublic(NotificationEntry entry); /** - * Has the given user chosen to allow their private (full) notifications to be shown even - * when the lockscreen is in "public" (secure & locked) mode? + * Do all sensitive notifications belonging to the given user require redaction when they are + * displayed in public? */ - boolean userAllowsPrivateNotificationsInPublic(int currentUserId); + boolean sensitiveNotifsNeedRedactionInPublic(int userId); /** * Has the given user chosen to allow notifications to be shown even when the lockscreen is in diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 56e09f03aa6b..334cfe5f4c41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.notification.stack.NotificationPrio import android.app.ActivityManager; import android.app.KeyguardManager; -import android.app.Notification; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -45,7 +44,6 @@ import android.util.SparseBooleanArray; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; @@ -60,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.ListenerSet; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; @@ -85,13 +84,12 @@ public class NotificationLockscreenUserManagerImpl implements private final DeviceProvisionedController mDeviceProvisionedController; private final KeyguardStateController mKeyguardStateController; private final SecureSettings mSecureSettings; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final Lazy<OverviewProxyService> mOverviewProxyService; private final Object mLock = new Object(); - - // Lazy - private NotificationEntryManager mEntryManager; - private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy; private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy; + private final Lazy<NotificationEntryManager> mEntryManagerLazy; private final DevicePolicyManager mDevicePolicyManager; private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray(); private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray(); @@ -103,13 +101,14 @@ public class NotificationLockscreenUserManagerImpl implements private final List<UserChangedListener> mListeners = new ArrayList<>(); private final BroadcastDispatcher mBroadcastDispatcher; private final NotificationClickNotifier mClickNotifier; - - private boolean mShowLockscreenNotifications; - private boolean mAllowLockscreenRemoteInput; - private LockPatternUtils mLockPatternUtils; - protected KeyguardManager mKeyguardManager; - private int mState = StatusBarState.SHADE; - private List<KeyguardNotificationSuppressor> mKeyguardSuppressors = new ArrayList<>(); + private final LockPatternUtils mLockPatternUtils; + private final List<KeyguardNotificationSuppressor> mKeyguardSuppressors = new ArrayList<>(); + protected final Context mContext; + private final Handler mMainHandler; + protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); + protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>(); + private final ListenerSet<Runnable> mOnSensitiveContentRedactionChangeListeners = + new ListenerSet<>(); protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { @Override @@ -120,7 +119,11 @@ public class NotificationLockscreenUserManagerImpl implements isCurrentProfile(getSendingUserId())) { mUsersAllowingPrivateNotifications.clear(); updateLockscreenNotificationSetting(); - getEntryManager().updateNotifications("ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED"); + for (Runnable listener : mOnSensitiveContentRedactionChangeListeners) { + listener.run(); + } + mEntryManagerLazy.get() + .updateNotifications("ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED"); } } }; @@ -142,7 +145,7 @@ public class NotificationLockscreenUserManagerImpl implements // The filtering needs to happen before the update call below in order to // make sure // the presenter has the updated notifications from the new user - getEntryManager().reapplyFilterAndSort("user switched"); + mEntryManagerLazy.get().reapplyFilterAndSort("user switched"); mPresenter.onUserSwitched(mCurrentUserId); for (UserChangedListener listener : mListeners) { @@ -156,7 +159,7 @@ public class NotificationLockscreenUserManagerImpl implements break; case Intent.ACTION_USER_UNLOCKED: // Start the overview connection to the launcher service - Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser(); + mOverviewProxyService.get().startConnectionToCurrentUser(); break; case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION: final IntentSender intentSender = intent.getParcelableExtra( @@ -179,28 +182,26 @@ public class NotificationLockscreenUserManagerImpl implements } }; - protected final Context mContext; - private final Handler mMainHandler; - protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); - protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>(); - - protected int mCurrentUserId = 0; + // Late-init protected NotificationPresenter mPresenter; protected ContentObserver mLockscreenSettingsObserver; protected ContentObserver mSettingsObserver; - private boolean mHideSilentNotificationsOnLockscreen; + protected KeyguardManager mKeyguardManager; - private NotificationEntryManager getEntryManager() { - if (mEntryManager == null) { - mEntryManager = Dependency.get(NotificationEntryManager.class); - } - return mEntryManager; - } + protected int mCurrentUserId = 0; + private int mState = StatusBarState.SHADE; + private boolean mHideSilentNotificationsOnLockscreen; + private boolean mShowLockscreenNotifications; + private boolean mAllowLockscreenRemoteInput; @Inject - public NotificationLockscreenUserManagerImpl(Context context, + public NotificationLockscreenUserManagerImpl( + Context context, BroadcastDispatcher broadcastDispatcher, DevicePolicyManager devicePolicyManager, + KeyguardUpdateMonitor keyguardUpdateMonitor, + Lazy<NotificationEntryManager> notificationEntryManagerLazy, + Lazy<OverviewProxyService> overviewProxyServiceLazy, UserManager userManager, Lazy<NotificationVisibilityProvider> visibilityProviderLazy, Lazy<CommonNotifCollection> commonNotifCollectionLazy, @@ -216,9 +217,11 @@ public class NotificationLockscreenUserManagerImpl implements mMainHandler = mainHandler; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; + mOverviewProxyService = overviewProxyServiceLazy; mCurrentUserId = ActivityManager.getCurrentUser(); mVisibilityProviderLazy = visibilityProviderLazy; mCommonNotifCollectionLazy = commonNotifCollectionLazy; + mEntryManagerLazy = notificationEntryManagerLazy; mClickNotifier = clickNotifier; statusBarStateController.addCallback(this); mLockPatternUtils = new LockPatternUtils(context); @@ -227,10 +230,12 @@ public class NotificationLockscreenUserManagerImpl implements mDeviceProvisionedController = deviceProvisionedController; mSecureSettings = secureSettings; mKeyguardStateController = keyguardStateController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; dumpManager.registerDumpable(this); } + @Override public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; @@ -243,7 +248,10 @@ public class NotificationLockscreenUserManagerImpl implements mUsersAllowingNotifications.clear(); // ... and refresh all the notifications updateLockscreenNotificationSetting(); - getEntryManager().updateNotifications("LOCK_SCREEN_SHOW_NOTIFICATIONS," + for (Runnable listener : mOnSensitiveContentRedactionChangeListeners) { + listener.run(); + } + mEntryManagerLazy.get().updateNotifications("LOCK_SCREEN_SHOW_NOTIFICATIONS," + " or LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS change"); } }; @@ -253,7 +261,7 @@ public class NotificationLockscreenUserManagerImpl implements public void onChange(boolean selfChange) { updateLockscreenNotificationSetting(); if (mDeviceProvisionedController.isDeviceProvisioned()) { - getEntryManager().updateNotifications("LOCK_SCREEN_ALLOW_REMOTE_INPUT" + mEntryManagerLazy.get().updateNotifications("LOCK_SCREEN_ALLOW_REMOTE_INPUT" + " or ZEN_MODE change"); } } @@ -312,14 +320,17 @@ public class NotificationLockscreenUserManagerImpl implements mSettingsObserver.onChange(false); // set up } + @Override public boolean shouldShowLockscreenNotifications() { return mShowLockscreenNotifications; } + @Override public boolean shouldAllowLockscreenRemoteInput() { return mAllowLockscreenRemoteInput; } + @Override public boolean isCurrentProfile(int userId) { synchronized (mLock) { return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; @@ -334,7 +345,7 @@ public class NotificationLockscreenUserManagerImpl implements if (userId == UserHandle.USER_ALL) { userId = mCurrentUserId; } - boolean inLockdown = Dependency.get(KeyguardUpdateMonitor.class).isUserInLockdown(userId); + boolean inLockdown = mKeyguardUpdateMonitor.isUserInLockdown(userId); mUsersInLockdownLatestResult.put(userId, inLockdown); return inLockdown; } @@ -343,6 +354,7 @@ public class NotificationLockscreenUserManagerImpl implements * Returns true if we're on a secure lockscreen and the user wants to hide notification data. * If so, notifications should be hidden. */ + @Override public boolean shouldHideNotifications(int userId) { boolean hide = isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId) || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId)) @@ -355,6 +367,7 @@ public class NotificationLockscreenUserManagerImpl implements * Returns true if we're on a secure lockscreen and the user wants to hide notifications via * package-specific override. */ + @Override public boolean shouldHideNotifications(String key) { if (mCommonNotifCollectionLazy.get() == null) { Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable()); @@ -365,6 +378,7 @@ public class NotificationLockscreenUserManagerImpl implements && visibleEntry.getRanking().getLockscreenVisibilityOverride() == VISIBILITY_SECRET; } + @Override public boolean shouldShowOnKeyguard(NotificationEntry entry) { if (mCommonNotifCollectionLazy.get() == null) { Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable()); @@ -387,14 +401,6 @@ public class NotificationLockscreenUserManagerImpl implements return mShowLockscreenNotifications && exceedsPriorityThreshold; } - private void setShowLockscreenNotifications(boolean show) { - mShowLockscreenNotifications = show; - } - - private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) { - mAllowLockscreenRemoteInput = allowLockscreenRemoteInput; - } - protected void updateLockscreenNotificationSetting() { final boolean show = mSecureSettings.getIntForUser( Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, @@ -408,7 +414,7 @@ public class NotificationLockscreenUserManagerImpl implements mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser( Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0; - setShowLockscreenNotifications(show && allowedByDpm); + mShowLockscreenNotifications = show && allowedByDpm; if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { final boolean remoteInput = mSecureSettings.getIntForUser( @@ -418,9 +424,9 @@ public class NotificationLockscreenUserManagerImpl implements final boolean remoteInputDpm = (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0; - setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm); + mAllowLockscreenRemoteInput = remoteInput && remoteInputDpm; } else { - setLockscreenAllowRemoteInput(false); + mAllowLockscreenRemoteInput = false; } } @@ -428,7 +434,7 @@ public class NotificationLockscreenUserManagerImpl implements * Has the given user chosen to allow their private (full) notifications to be shown even * when the lockscreen is in "public" (secure & locked) mode? */ - public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { + protected boolean userAllowsPrivateNotificationsInPublic(int userHandle) { if (userHandle == UserHandle.USER_ALL) { return true; } @@ -473,10 +479,12 @@ public class NotificationLockscreenUserManagerImpl implements /** * Save the current "public" (locked and secure) state of the lockscreen. */ + @Override public void setLockscreenPublicMode(boolean publicMode, int userId) { mLockscreenPublicMode.put(userId, publicMode); } + @Override public boolean isLockscreenPublicMode(int userId) { if (userId == UserHandle.USER_ALL) { return mLockscreenPublicMode.get(mCurrentUserId, false); @@ -493,6 +501,7 @@ public class NotificationLockscreenUserManagerImpl implements * Has the given user chosen to allow notifications to be shown even when the lockscreen is in * "public" (secure & locked) mode? */ + @Override public boolean userAllowsNotificationsInPublic(int userHandle) { if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { return true; @@ -513,36 +522,37 @@ public class NotificationLockscreenUserManagerImpl implements } /** @return true if the entry needs redaction when on the lockscreen. */ - public boolean needsRedaction(NotificationEntry ent) { + @Override + public boolean notifNeedsRedactionInPublic(NotificationEntry ent) { int userId = ent.getSbn().getUserId(); + return ent.hasSensitiveContents() && sensitiveNotifsNeedRedactionInPublic(userId); + } + @Override + public boolean sensitiveNotifsNeedRedactionInPublic(int userId) { boolean isCurrentUserRedactingNotifs = !userAllowsPrivateNotificationsInPublic(mCurrentUserId); + if (userId == mCurrentUserId) { + return isCurrentUserRedactingNotifs; + } + boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId); boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId); // redact notifications if the current user is redacting notifications; however if the // notification is associated with a managed profile, we rely on the managed profile // setting to determine whether to redact it - boolean isNotifRedacted = (!isNotifForManagedProfile && isCurrentUserRedactingNotifs) - || isNotifUserRedacted; - - boolean notificationRequestsRedaction = - ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE; - boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey()); + return (!isNotifForManagedProfile && isCurrentUserRedactingNotifs) || isNotifUserRedacted; + } - return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted; + @Override + public void addOnNeedsRedactionInPublicChangedListener(Runnable listener) { + mOnSensitiveContentRedactionChangeListeners.addIfAbsent(listener); } - private boolean packageHasVisibilityOverride(String key) { - if (mCommonNotifCollectionLazy.get() == null) { - Log.wtf(TAG, "mEntryManager was null!", new Throwable()); - return true; - } - NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); - return entry != null - && entry.getRanking().getLockscreenVisibilityOverride() - == Notification.VISIBILITY_PRIVATE; + @Override + public void removeOnNeedsRedactionInPublicChangedListener(Runnable listener) { + mOnSensitiveContentRedactionChangeListeners.remove(listener); } private void updateCurrentProfilesCache() { @@ -562,12 +572,16 @@ public class NotificationLockscreenUserManagerImpl implements for (UserChangedListener listener : mListeners) { listener.onCurrentProfilesChanged(mCurrentProfiles); } + for (Runnable listener : mOnSensitiveContentRedactionChangeListeners) { + listener.run(); + } }); } /** * If any of the profiles are in public mode. */ + @Override public boolean isAnyProfilePublicMode() { synchronized (mLock) { for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { @@ -596,10 +610,12 @@ public class NotificationLockscreenUserManagerImpl implements /** * Returns the current user id. This can change if the user is switched. */ + @Override public int getCurrentUserId() { return mCurrentUserId; } + @Override public SparseArray<UserInfo> getCurrentProfiles() { return mCurrentProfiles; } @@ -640,7 +656,8 @@ public class NotificationLockscreenUserManagerImpl implements setLockscreenPublicMode(isProfilePublic, userId); mUsersWithSeparateWorkChallenge.put(userId, needsSeparateChallenge); } - getEntryManager().updateNotifications("NotificationLockscreenUserManager.updatePublicMode"); + mEntryManagerLazy.get() + .updateNotifications("NotificationLockscreenUserManager.updatePublicMode"); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 734bc48093b2..0d604014e8f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -326,9 +326,9 @@ public class NotificationShelf extends ActivatableNotificationView implements || child.isPinned(); boolean isLastChild = child == lastChild; final float viewStart = child.getTranslationY(); - - final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast, - expandingAnimated, isLastChild); + final float shelfClipStart = getTranslationY() - mPaddingBetweenElements; + final float inShelfAmount = getAmountInShelf(i, child, scrollingFast, + expandingAnimated, isLastChild, shelfClipStart); // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) { @@ -609,10 +609,18 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** - * @return the amount how much this notification is in the shelf + * @param i Index of the view in the host layout. + * @param view The current ExpandableView. + * @param scrollingFast Whether we are scrolling fast. + * @param expandingAnimated Whether we are expanding a notification. + * @param isLastChild Whether this is the last view. + * @param shelfClipStart The point at which notifications start getting clipped by the shelf. + * @return The amount how much this notification is in the shelf. + * 0f is not in shelf. 1f is completely in shelf. */ - private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast, - boolean expandingAnimated, boolean isLastChild) { + @VisibleForTesting + public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast, + boolean expandingAnimated, boolean isLastChild, float shelfClipStart) { // Let's calculate how much the view is in the shelf float viewStart = view.getTranslationY(); @@ -635,29 +643,33 @@ public class NotificationShelf extends ActivatableNotificationView implements float viewEnd = viewStart + fullHeight; float fullTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f; - float shelfStart = getTranslationY() - mPaddingBetweenElements; + + // Don't animate shelf icons during shade expansion. if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) { // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) { fullTransitionAmount = 1f; iconTransitionAmount = 1f; } - } else if (viewEnd >= shelfStart + + } else if (viewEnd >= shelfClipStart && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) && (mAmbientState.isShadeExpanded() || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { - if (viewStart < shelfStart) { - float fullAmount = (shelfStart - viewStart) / fullHeight; + if (viewStart < shelfClipStart && Math.abs(viewStart - shelfClipStart) > 0.001f) { + // Partially clipped by shelf. + float fullAmount = (shelfClipStart - viewStart) / fullHeight; fullAmount = Math.min(1.0f, fullAmount); fullTransitionAmount = 1.0f - fullAmount; if (isLastChild) { // Reduce icon transform distance to completely fade in shelf icon // by the time the notification icon fades out, and vice versa - iconTransitionAmount = (shelfStart - viewStart) + iconTransitionAmount = (shelfClipStart - viewStart) / (iconTransformStart - viewStart); } else { - iconTransitionAmount = (shelfStart - iconTransformStart) / transformDistance; + iconTransitionAmount = (shelfClipStart - iconTransformStart) + / transformDistance; } iconTransitionAmount = MathUtils.constrain(iconTransitionAmount, 0.0f, 1.0f); iconTransitionAmount = 1.0f - iconTransitionAmount; @@ -772,6 +784,9 @@ public class NotificationShelf extends ActivatableNotificationView implements } private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) { + if (mShelfIcons == null) { + return null; + } return mShelfIcons.getIconState(icon); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 054543c7d2b1..30cb09d56f17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -53,6 +53,7 @@ import com.android.wm.shell.bubbles.Bubbles; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Stack; @@ -207,12 +208,9 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) { userPublic = false; } - boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent); + boolean needsRedaction = mLockscreenUserManager.notifNeedsRedactionInPublic(ent); boolean sensitive = userPublic && needsRedaction; - boolean deviceSensitive = devicePublic - && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( - currentUserId); - ent.setSensitive(sensitive, deviceSensitive); + ent.getRow().setSensitive(sensitive); ent.getRow().setNeedsRedaction(needsRedaction); mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow()); boolean isChildInGroup = mGroupManager.isChildInGroup(ent); @@ -365,6 +363,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle boolean hasClearableAlertingNotifs = false; boolean hasNonClearableSilentNotifs = false; boolean hasClearableSilentNotifs = false; + HashSet<Integer> clearableAlertingSensitiveNotifUsers = new HashSet<>(); + HashSet<Integer> clearableSilentSensitiveNotifUsers = new HashSet<>(); final int childCount = mListContainer.getContainerChildCount(); int visibleTopLevelEntries = 0; for (int i = 0; i < childCount; i++) { @@ -376,10 +376,11 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle continue; } final ExpandableNotificationRow row = (ExpandableNotificationRow) child; - boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT; + NotificationEntry entry = row.getEntry(); + boolean isSilent = entry.getBucket() == BUCKET_SILENT; // NOTE: NotificationEntry.isClearable() will internally check group children to ensure // the group itself definitively clearable. - boolean isClearable = row.getEntry().isClearable(); + boolean isClearable = entry.isClearable(); visibleTopLevelEntries++; if (isSilent) { if (isClearable) { @@ -394,13 +395,24 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle hasNonClearableAlertingNotifs = true; } } + if (isClearable && entry.hasSensitiveContents()) { + int userId = entry.getSbn().getUserId(); + if (isSilent) { + clearableSilentSensitiveNotifUsers.add(userId); + } else { + clearableAlertingSensitiveNotifUsers.add(userId); + } + } } mStackController.setNotifStats(new NotifStats( visibleTopLevelEntries /* numActiveNotifs */, hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */, hasClearableAlertingNotifs /* hasClearableAlertingNotifs */, hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */, - hasClearableSilentNotifs /* hasClearableSilentNotifs */ + hasClearableSilentNotifs /* hasClearableSilentNotifs */, + clearableAlertingSensitiveNotifUsers /* clearableAlertingSensitiveNotifUsers */, + clearableSilentSensitiveNotifUsers /* clearableSilentSensitiveNotifUsers */ + )); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java index a0ccd5726c75..d16e9e5d7faf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java @@ -80,7 +80,7 @@ public class DynamicPrivacyController implements KeyguardStateController.Callbac @VisibleForTesting boolean isDynamicPrivacyEnabled() { - return !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( + return mLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic( mLockscreenUserManager.getCurrentUserId()); } @@ -95,6 +95,10 @@ public class DynamicPrivacyController implements KeyguardStateController.Callbac mListeners.add(listener); } + public void removeListener(Listener listener) { + mListeners.remove(listener); + } + /** * Is the notification shade currently in a locked down mode where it's fully showing but the * contents aren't revealed yet? 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 4fc347a09292..e3c39ddad145 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 @@ -169,9 +169,6 @@ public final class NotificationEntry extends ListEntry { */ private boolean hasSentReply; - private boolean mSensitive = true; - private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>(); - private boolean mAutoHeadsUp; private boolean mPulseSupressed; private int mBucket = BUCKET_ALERTING; @@ -867,33 +864,29 @@ public final class NotificationEntry extends ListEntry { } /** - * Set this notification to be sensitive. - * - * @param sensitive true if the content of this notification is sensitive right now - * @param deviceSensitive true if the device in general is sensitive right now - */ - public void setSensitive(boolean sensitive, boolean deviceSensitive) { - getRow().setSensitive(sensitive, deviceSensitive); - if (sensitive != mSensitive) { - mSensitive = sensitive; - for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) { - mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this); - } + * Returns the visibility of this notification on the lockscreen, taking into account both the + * notification's defined visibility, as well as the visibility override as determined by the + * device policy. + */ + public int getLockscreenVisibility() { + int setting = mRanking.getLockscreenVisibilityOverride(); + if (setting == Ranking.VISIBILITY_NO_OVERRIDE) { + setting = mSbn.getNotification().visibility; } + return setting; } - public boolean isSensitive() { - return mSensitive; - } - - /** Add a listener to be notified when the entry's sensitivity changes. */ - public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) { - mOnSensitivityChangedListeners.add(listener); - } - - /** Remove a listener that was registered above. */ - public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) { - mOnSensitivityChangedListeners.remove(listener); + /** + * Does this notification contain sensitive content? If the user's settings specify, then this + * content would need to be redacted when the device this public. + * + * NOTE: If the notification's visibility setting is VISIBILITY_SECRET, then this will return + * false; SECRET notifications are omitted entirely when the device is public, so effectively + * the contents of the notification are not sensitive whenever the notification is actually + * visible. + */ + public boolean hasSensitiveContents() { + return getLockscreenVisibility() == Notification.VISIBILITY_PRIVATE; } public boolean isPulseSuppressed() { @@ -954,12 +947,6 @@ public final class NotificationEntry extends ListEntry { } } - /** Listener interface for {@link #addOnSensitivityChangedListener} */ - public interface OnSensitivityChangedListener { - /** Called when the sensitivity changes */ - void onSensitivityChanged(@NonNull NotificationEntry entry); - } - /** @see #getDismissState() */ public enum DismissState { /** User has not dismissed this notif or its parent */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 1b5e52d7f8fb..df2fe4e8511f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -93,6 +93,7 @@ public class ShadeListBuilder implements Dumpable { private final NotificationInteractionTracker mInteractionTracker; private final DumpManager mDumpManager; // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated + // TODO replace temp with collection pool for readability private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>(); private final boolean mAlwaysLogList; @@ -230,13 +231,7 @@ public class ShadeListBuilder implements Dumpable { mPipelineState.requireState(STATE_IDLE); mNotifSections.clear(); - NotifSectioner lastSection = null; for (NotifSectioner sectioner : sectioners) { - if (lastSection != null && lastSection.getBucket() > sectioner.getBucket()) { - throw new IllegalArgumentException("setSectioners with non contiguous sections " - + lastSection.getName() + " - " + lastSection.getBucket() + " & " - + sectioner.getName() + " - " + sectioner.getBucket()); - } final NotifSection section = new NotifSection(sectioner, mNotifSections.size()); final NotifComparator sectionComparator = section.getComparator(); mNotifSections.add(section); @@ -244,10 +239,23 @@ public class ShadeListBuilder implements Dumpable { if (sectionComparator != null) { sectionComparator.setInvalidationListener(this::onNotifComparatorInvalidated); } - lastSection = sectioner; } mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size())); + + // validate sections + final ArraySet<Integer> seenBuckets = new ArraySet<>(); + int lastBucket = mNotifSections.size() > 0 + ? mNotifSections.get(0).getBucket() + : 0; + for (NotifSection section : mNotifSections) { + if (lastBucket != section.getBucket() && seenBuckets.contains(section.getBucket())) { + throw new IllegalStateException("setSectioners with non contiguous sections " + + section.getLabel() + " has an already seen bucket"); + } + lastBucket = section.getBucket(); + seenBuckets.add(lastBucket); + } } void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 3516625cc471..b24d2922adfb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -56,7 +56,6 @@ class NotifCoordinatorsImpl @Inject constructor( smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, viewConfigCoordinator: ViewConfigCoordinator, visualStabilityCoordinator: VisualStabilityCoordinator, - sensitiveContentCoordinator: SensitiveContentCoordinator, activityLaunchAnimCoordinator: ActivityLaunchAnimCoordinator ) : NotifCoordinators { @@ -94,7 +93,6 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(shadeEventCoordinator) mCoordinators.add(viewConfigCoordinator) mCoordinators.add(visualStabilityCoordinator) - mCoordinators.add(sensitiveContentCoordinator) mCoordinators.add(activityLaunchAnimCoordinator) if (notifPipelineFlags.isSmartspaceDedupingEnabled()) { mCoordinators.add(smartspaceDedupingCoordinator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 57fd1975e13a..56484010c213 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator; -import android.annotation.NonNull; import android.annotation.Nullable; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -29,13 +28,11 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import java.util.Collections; -import java.util.List; import javax.inject.Inject; @@ -53,10 +50,10 @@ public class RankingCoordinator implements Coordinator { private final HighPriorityProvider mHighPriorityProvider; private final SectionClassifier mSectionClassifier; private final NodeController mSilentNodeController; - private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; - private boolean mHasSilentEntries; - private boolean mHasMinimizedEntries; + private final AlertingNotifSectioner mAlertingNotifSectioner = new AlertingNotifSectioner(); + private final SilentNotifSectioner mSilentNotifSectioner = new SilentNotifSectioner(); + private final MinimizedNotifSectioner mMinimizedNotifSectioner = new MinimizedNotifSectioner(); @Inject public RankingCoordinator( @@ -64,14 +61,12 @@ public class RankingCoordinator implements Coordinator { HighPriorityProvider highPriorityProvider, SectionClassifier sectionClassifier, @AlertingHeader NodeController alertingHeaderController, - @SilentHeader SectionHeaderController silentHeaderController, @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; mSectionClassifier = sectionClassifier; mAlertingHeaderController = alertingHeaderController; mSilentNodeController = silentNodeController; - mSilentHeaderController = silentHeaderController; } @Override @@ -95,8 +90,44 @@ public class RankingCoordinator implements Coordinator { return mMinimizedNotifSectioner; } - private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting", - NotificationPriorityBucketKt.BUCKET_ALERTING) { + /** + * Checks whether to filter out the given notification based the notification's Ranking object. + * NotifListBuilder invalidates the notification list each time the ranking is updated, + * so we don't need to explicitly invalidate this filter on ranking update. + */ + private final NotifFilter mSuspendedFilter = new NotifFilter("IsSuspendedFilter") { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return entry.getRanking().isSuspended(); + } + }; + + private final NotifFilter mDndVisualEffectsFilter = new NotifFilter( + "DndSuppressingVisualEffects") { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) { + return true; + } + + return !mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList(); + } + }; + + private final StatusBarStateController.StateListener mStatusBarStateCallback = + new StatusBarStateController.StateListener() { + @Override + public void onDozingChanged(boolean isDozing) { + mDndVisualEffectsFilter.invalidateList(); + } + }; + + private class AlertingNotifSectioner extends NotifSectioner { + + AlertingNotifSectioner() { + super("Alerting", NotificationPriorityBucketKt.BUCKET_ALERTING); + } + @Override public boolean isInSection(ListEntry entry) { return mHighPriorityProvider.isHighPriority(entry); @@ -111,10 +142,14 @@ public class RankingCoordinator implements Coordinator { } return null; } - }; + } + + private class SilentNotifSectioner extends NotifSectioner { + + SilentNotifSectioner() { + super("Silent", NotificationPriorityBucketKt.BUCKET_SILENT); + } - private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent", - NotificationPriorityBucketKt.BUCKET_SILENT) { @Override public boolean isInSection(ListEntry entry) { return !mHighPriorityProvider.isHighPriority(entry) @@ -126,24 +161,14 @@ public class RankingCoordinator implements Coordinator { public NodeController getHeaderNodeController() { return mSilentNodeController; } + } - @Nullable - @Override - public void onEntriesUpdated(@NonNull List<ListEntry> entries) { - mHasSilentEntries = false; - for (int i = 0; i < entries.size(); i++) { - if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { - mHasSilentEntries = true; - break; - } - } - mSilentHeaderController.setClearSectionEnabled( - mHasSilentEntries | mHasMinimizedEntries); + private class MinimizedNotifSectioner extends NotifSectioner { + + MinimizedNotifSectioner() { + super("Minimized", NotificationPriorityBucketKt.BUCKET_SILENT); } - }; - private final NotifSectioner mMinimizedNotifSectioner = new NotifSectioner("Minimized", - NotificationPriorityBucketKt.BUCKET_SILENT) { @Override public boolean isInSection(ListEntry entry) { return !mHighPriorityProvider.isHighPriority(entry) @@ -155,51 +180,5 @@ public class RankingCoordinator implements Coordinator { public NodeController getHeaderNodeController() { return mSilentNodeController; } - - @Nullable - @Override - public void onEntriesUpdated(@NonNull List<ListEntry> entries) { - mHasMinimizedEntries = false; - for (int i = 0; i < entries.size(); i++) { - if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { - mHasMinimizedEntries = true; - break; - } - } - mSilentHeaderController.setClearSectionEnabled( - mHasSilentEntries | mHasMinimizedEntries); - } - }; - - /** - * Checks whether to filter out the given notification based the notification's Ranking object. - * NotifListBuilder invalidates the notification list each time the ranking is updated, - * so we don't need to explicitly invalidate this filter on ranking update. - */ - private final NotifFilter mSuspendedFilter = new NotifFilter("IsSuspendedFilter") { - @Override - public boolean shouldFilterOut(NotificationEntry entry, long now) { - return entry.getRanking().isSuspended(); - } - }; - - private final NotifFilter mDndVisualEffectsFilter = new NotifFilter( - "DndSuppressingVisualEffects") { - @Override - public boolean shouldFilterOut(NotificationEntry entry, long now) { - if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) { - return true; - } - - return !mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList(); - } - }; - - private final StatusBarStateController.StateListener mStatusBarStateCallback = - new StatusBarStateController.StateListener() { - @Override - public void onDozingChanged(boolean isDozing) { - mDndVisualEffectsFilter.invalidateList(); - } - }; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt deleted file mode 100644 index 3f8a39f62dfb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator - -import android.os.UserHandle -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.NotificationLockscreenUserManager -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.DynamicPrivacyController -import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry -import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope -import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator -import com.android.systemui.statusbar.policy.KeyguardStateController -import dagger.Binds -import dagger.Module -import javax.inject.Inject - -@Module(includes = [PrivateSensitiveContentCoordinatorModule::class]) -interface SensitiveContentCoordinatorModule - -@Module -private interface PrivateSensitiveContentCoordinatorModule { - @Binds - fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator -} - -/** Coordinates re-inflation and post-processing of sensitive notification content. */ -interface SensitiveContentCoordinator : Coordinator - -@CoordinatorScope -private class SensitiveContentCoordinatorImpl @Inject constructor( - private val dynamicPrivacyController: DynamicPrivacyController, - private val lockscreenUserManager: NotificationLockscreenUserManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val statusBarStateController: StatusBarStateController, - private val keyguardStateController: KeyguardStateController -) : Invalidator("SensitiveContentInvalidator"), - SensitiveContentCoordinator, - DynamicPrivacyController.Listener, - OnBeforeRenderListListener { - - override fun attach(pipeline: NotifPipeline) { - dynamicPrivacyController.addListener(this) - pipeline.addOnBeforeRenderListListener(this) - pipeline.addPreRenderInvalidator(this) - } - - override fun onDynamicPrivacyChanged(): Unit = invalidateList() - - override fun onBeforeRenderList(entries: List<ListEntry>) { - if (keyguardStateController.isKeyguardGoingAway() || - statusBarStateController.getState() == StatusBarState.KEYGUARD && - keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing( - KeyguardUpdateMonitor.getCurrentUser())) { - // don't update yet if: - // - the keyguard is currently going away - // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash) - - // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the - // dependent state changes invalidate the pipeline - return - } - - val currentUserId = lockscreenUserManager.currentUserId - val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) - val deviceSensitive = devicePublic && - !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId) - val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked - for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { - val notifUserId = entry.sbn.user.identifier - val userLockscreen = devicePublic || - lockscreenUserManager.isLockscreenPublicMode(notifUserId) - val userPublic = when { - // if we're not on the lockscreen, we're definitely private - !userLockscreen -> false - // we are on the lockscreen, so unless we're dynamically unlocked, we're - // definitely public - !dynamicallyUnlocked -> true - // we're dynamically unlocked, but check if the notification needs - // a separate challenge if it's from a work profile - else -> when (notifUserId) { - currentUserId -> false - UserHandle.USER_ALL -> false - else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId) - } - } - val needsRedaction = lockscreenUserManager.needsRedaction(entry) - val isSensitive = userPublic && needsRedaction - entry.setSensitive(isSensitive, deviceSensitive) - } - } -} - -private fun extractAllRepresentativeEntries( - entries: List<ListEntry> -): Sequence<NotificationEntry> = - entries.asSequence().flatMap(::extractAllRepresentativeEntries) - -private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> = - sequence { - listEntry.representativeEntry?.let { yield(it) } - if (listEntry is GroupEntry) { - yieldAll(extractAllRepresentativeEntries(listEntry.children)) - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 1c96e8ceb27f..2374e9c58177 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -50,6 +50,8 @@ class StackCoordinator @Inject internal constructor( var hasClearableAlertingNotifs = false var hasNonClearableSilentNotifs = false var hasClearableSilentNotifs = false + val clearableAlertingSensitiveNotifUsers = mutableSetOf<Int>() + val clearableSilentSensitiveNotifUsers = mutableSetOf<Int>() entries.forEach { val section = checkNotNull(it.section) { "Null section for ${it.key}" } val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" } @@ -63,13 +65,22 @@ class StackCoordinator @Inject internal constructor( !isSilent && isClearable -> hasClearableAlertingNotifs = true !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true } + if (isClearable && entry.hasSensitiveContents()) { + if (isSilent) { + clearableSilentSensitiveNotifUsers.add(entry.sbn.userId) + } else { + clearableAlertingSensitiveNotifUsers.add(entry.sbn.userId) + } + } } return NotifStats( numActiveNotifs = entries.size, hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs, hasClearableAlertingNotifs = hasClearableAlertingNotifs, hasNonClearableSilentNotifs = hasNonClearableSilentNotifs, - hasClearableSilentNotifs = hasClearableSilentNotifs + hasClearableSilentNotifs = hasClearableSilentNotifs, + clearableAlertingSensitiveNotifUsers = clearableAlertingSensitiveNotifUsers, + clearableSilentSensitiveNotifUsers = clearableSilentSensitiveNotifUsers ) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt index 274affd9da43..8ecffcb7670a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt @@ -20,7 +20,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.coordinator.ActivityLaunchAnimCoordinatorModule import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl -import com.android.systemui.statusbar.notification.collection.coordinator.SensitiveContentCoordinatorModule import dagger.Binds import dagger.Module import dagger.Provides @@ -50,7 +49,6 @@ interface CoordinatorsSubcomponent { @Module(includes = [ ActivityLaunchAnimCoordinatorModule::class, - SensitiveContentCoordinatorModule::class, ]) private abstract class InternalCoordinatorsModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 4c7b2bbfb6d9..6e96aad776d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.inflation; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; + import static java.util.Objects.requireNonNull; import android.annotation.Nullable; @@ -249,10 +251,13 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { RowContentBindParams params = mRowContentBindStage.getStageParams(entry); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); params.setUseLowPriority(isLowPriority); - - // TODO: Replace this API with RowContentBindParams directly. Also move to a separate - // redaction controller. - row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); + boolean needsRedaction = + mNotificationLockscreenUserManager.notifNeedsRedactionInPublic(entry); + if (needsRedaction) { + params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); + } else { + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); + } params.rebindAllContentViews(); mRowContentBindStage.requestRebind(entry, en -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index 6db544c77f87..8be710c8842c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -57,7 +57,6 @@ class NodeSpecBuilder( var currentSection: NotifSection? = null val prevSections = mutableSetOf<NotifSection?>() - var lastSection: NotifSection? = null val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible val sectionOrder = mutableListOf<NotifSection?>() val sectionHeaders = mutableMapOf<NotifSection?, NodeController?>() @@ -65,15 +64,6 @@ class NodeSpecBuilder( for (entry in notifList) { val section = entry.section!! - - lastSection?.let { - if (it.bucket > section.bucket) { - throw IllegalStateException("buildNodeSpec with non contiguous section " + - "buckets ${it.sectioner.name} - ${it.bucket} & " + - "${it.sectioner.name} - ${it.bucket}") - } - } - lastSection = section if (prevSections.contains(section)) { throw java.lang.RuntimeException("Section ${section.label} has been duplicated") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt index b6278d1d5f01..580d853dc5e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt @@ -28,11 +28,21 @@ data class NotifStats( val hasNonClearableAlertingNotifs: Boolean, val hasClearableAlertingNotifs: Boolean, val hasNonClearableSilentNotifs: Boolean, - val hasClearableSilentNotifs: Boolean + val hasClearableSilentNotifs: Boolean, + val clearableAlertingSensitiveNotifUsers: Set<Int>, + val clearableSilentSensitiveNotifUsers: Set<Int> ) { companion object { @JvmStatic - val empty = NotifStats(0, false, false, false, false) + val empty = NotifStats( + numActiveNotifs = 0, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + clearableAlertingSensitiveNotifUsers = emptySet(), + clearableSilentSensitiveNotifUsers = emptySet(), + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt index 2a9cfd034dce..f58918fe6f80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt @@ -182,4 +182,4 @@ annotation class HeaderClickAction @Scope @Retention(AnnotationRetention.BINARY) -annotation class SectionHeaderScope
\ No newline at end of file +annotation class SectionHeaderScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index d8965418b4c4..015e3d8cd553 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -28,6 +28,7 @@ import android.widget.ImageView import com.android.internal.statusbar.StatusBarIcon import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -49,31 +50,29 @@ import javax.inject.Inject class IconManager @Inject constructor( private val notifCollection: CommonNotifCollection, private val launcherApps: LauncherApps, - private val iconBuilder: IconBuilder + private val iconBuilder: IconBuilder, + private val notifLockscreenUserManager: NotificationLockscreenUserManager ) : ConversationIconManager { private var unimportantConversationKeys: Set<String> = emptySet() fun attach() { notifCollection.addCollectionListener(entryListener) + notifLockscreenUserManager.addOnNeedsRedactionInPublicChangedListener(sensitivityListener) } private val entryListener = object : NotifCollectionListener { - override fun onEntryInit(entry: NotificationEntry) { - entry.addOnSensitivityChangedListener(sensitivityListener) - } - - override fun onEntryCleanUp(entry: NotificationEntry) { - entry.removeOnSensitivityChangedListener(sensitivityListener) - } - override fun onRankingApplied() { // rankings affect whether a conversation is important, which can change the icons recalculateForImportantConversationChange() } } - private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener { - entry -> updateIconsSafe(entry) + private val sensitivityListener = Runnable { + for (entry in notifCollection.allNotifs) { + if (entry.hasSensitiveContents()) { + updateIconsSafe(entry) + } + } } private fun recalculateForImportantConversationChange() { @@ -182,12 +181,16 @@ class IconManager @Inject constructor( } } + private inline val NotificationEntry.needsRedactionInPublic: Boolean get() = + hasSensitiveContents() && + notifLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(sbn.userId) + @Throws(InflationException::class) private fun getIconDescriptors( entry: NotificationEntry ): Pair<StatusBarIcon, StatusBarIcon> { val iconDescriptor = getIconDescriptor(entry, false /* redact */) - val sensitiveDescriptor = if (entry.isSensitive) { + val sensitiveDescriptor = if (entry.needsRedactionInPublic) { getIconDescriptor(entry, true /* redact */) } else { iconDescriptor @@ -310,7 +313,7 @@ class IconManager @Inject constructor( iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon) return isImportantConversation(entry) && !isSmallIcon && - (!usedInSensitiveContext || !entry.isSensitive) + (!usedInSensitiveContext || !entry.needsRedactionInPublic) } private fun isImportantConversation(entry: NotificationEntry): Boolean { @@ -338,4 +341,4 @@ interface ConversationIconManager { * of a group from which the priority notification has been removed. */ fun setUnimportantConversations(keys: Collection<String>) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index 6b79680fdb26..f0b221dd4726 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -160,8 +160,7 @@ class ChannelEditorDialogController @Inject constructor( val channels = groupList .flatMap { group -> group.channels.asSequence().filterNot { channel -> - channel.isImportanceLockedByOEM || - channel.importance == IMPORTANCE_NONE || + channel.importance == IMPORTANCE_NONE || channel.isImportanceLockedByCriticalDeviceFunction } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 0ae365352df0..f975799b4b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -60,6 +60,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; import android.util.Property; +import android.util.Slog; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -93,8 +94,8 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; -import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.FeedbackIcon; +import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -206,7 +207,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** Are we showing the "public" version */ private boolean mShowingPublic; private boolean mSensitive; - private boolean mSensitiveHiddenInGeneral; private boolean mShowingPublicInitialized; private boolean mHideSensitiveForIntrinsicHeight; private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT; @@ -364,9 +364,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Nullable private OnExpansionChangedListener mExpansionChangedListener; @Nullable private Runnable mOnIntrinsicHeightReachedRunnable; - private SystemNotificationAsyncTask mSystemNotificationAsyncTask = - new SystemNotificationAsyncTask(); - private float mTopRoundnessDuringLaunchAnimation; private float mBottomRoundnessDuringLaunchAnimation; @@ -376,44 +373,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * calls. */ private static Boolean isSystemNotification(Context context, StatusBarNotification sbn) { - // TODO (b/194833441): clean up before launch - if (Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1) { - INotificationManager iNm = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - - boolean isSystem = false; - try { - isSystem = iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId()); - } catch (RemoteException e) { - Log.e(TAG, "cannot reach NMS"); - } - RoleManager rm = context.getSystemService(RoleManager.class); - List<String> fixedRoleHolders = new ArrayList<>(); - fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_DIALER)); - fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_EMERGENCY)); - if (fixedRoleHolders.contains(sbn.getPackageName())) { - isSystem = true; - } + INotificationManager iNm = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - return isSystem; - } else { - PackageManager packageManager = CentralSurfaces.getPackageManagerForUser( - context, sbn.getUser().getIdentifier()); - Boolean isSystemNotification = null; - - try { - PackageInfo packageInfo = packageManager.getPackageInfo( - sbn.getPackageName(), PackageManager.GET_SIGNATURES); - - isSystemNotification = - com.android.settingslib.Utils.isSystemPackage( - context.getResources(), packageManager, packageInfo); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "cacheIsSystemNotification: Could not find package info"); - } - return isSystemNotification; + boolean isSystem = false; + try { + isSystem = iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId()); + } catch (RemoteException e) { + Log.e(TAG, "cannot reach NMS"); } + RoleManager rm = context.getSystemService(RoleManager.class); + List<String> fixedRoleHolders = new ArrayList<>(); + fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_DIALER)); + fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_EMERGENCY)); + if (fixedRoleHolders.contains(sbn.getPackageName())) { + isSystem = true; + } + + return isSystem; } public NotificationContentView[] getLayouts() { @@ -538,47 +515,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Caches whether or not this row contains a system notification. Note, this is only cached - * once per notification as the packageInfo can't technically change for a notification row. - */ - private void cacheIsSystemNotification() { - //TODO: This probably shouldn't be in ExpandableNotificationRow - if (mEntry != null && mEntry.mIsSystemNotification == null) { - if (mSystemNotificationAsyncTask.getStatus() == AsyncTask.Status.PENDING) { - // Run async task once, only if it hasn't already been executed. Note this is - // executed in serial - no need to parallelize this small task. - mSystemNotificationAsyncTask.execute(); - } - } - } - - /** * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif * or is in an allowList). */ public boolean getIsNonblockable() { - // If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once - // again, but in-place on the main thread this time. This should rarely ever get called. - if (mEntry != null && mEntry.mIsSystemNotification == null) { - if (DEBUG) { - Log.d(TAG, "Retrieving isSystemNotification on main thread"); - } - mSystemNotificationAsyncTask.cancel(true /* mayInterruptIfRunning */); - mEntry.mIsSystemNotification = isSystemNotification(mContext, mEntry.getSbn()); + if (mEntry == null || mEntry.getChannel() == null) { + Log.w(TAG, "missing entry or channel"); + return true; } - - // TODO (b/194833441): remove when we've migrated to permission - boolean isNonblockable = mEntry.getChannel().isImportanceLockedByOEM() - || mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction(); - - if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) { - if (mEntry.mIsSystemNotification) { - if (mEntry.getChannel() != null && !mEntry.getChannel().isBlockable()) { - isNonblockable = true; - } - } + if (mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction() + && !mEntry.getChannel().isBlockable()) { + return true; } - return isNonblockable; + + return false; } private boolean isConversation() { @@ -1560,6 +1510,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mUseIncreasedHeadsUpHeight = use; } + // TODO: remove this method and mNeedsRedaction entirely once the old pipeline is gone public void setNeedsRedaction(boolean needsRedaction) { // TODO: Move inflation logic out of this call and remove this method if (mNeedsRedaction != needsRedaction) { @@ -1648,8 +1599,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mBubblesManagerOptional = bubblesManagerOptional; mNotificationGutsManager = gutsManager; mMetricsLogger = metricsLogger; - - cacheIsSystemNotification(); } private void initDimens() { @@ -2644,9 +2593,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); } - public void setSensitive(boolean sensitive, boolean hideSensitive) { + public void setSensitive(boolean sensitive) { mSensitive = sensitive; - mSensitiveHiddenInGeneral = hideSensitive; } @Override @@ -2736,7 +2684,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * see {@link NotificationEntry#isDismissable()}. */ public boolean canViewBeDismissed() { - return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + // Entry not dismissable. + if (!mEntry.isDismissable()) { + return false; + } + // Entry shouldn't be showing the public layout, it can be dismissed. + if (!shouldShowPublic()) { + return true; + } + return false; } /** @@ -2745,7 +2701,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * clearability see {@link NotificationEntry#isClearable()}. */ public boolean canViewBeCleared() { - return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + return mEntry.isClearable() && !shouldShowPublic(); } private boolean shouldShowPublic() { @@ -3509,10 +3465,28 @@ public class ExpandableNotificationRow extends ActivatableNotificationView pw.print(", translation: " + getTranslation()); pw.print(", removed: " + isRemoved()); pw.print(", expandAnimationRunning: " + mExpandAnimationRunning); - NotificationContentView showingLayout = getShowingLayout(); - pw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); - pw.println(); - showingLayout.dump(pw, args); + pw.print(", sensitive: " + mSensitive); + pw.print(", hideSensitiveForIntrinsicHeight: " + mHideSensitiveForIntrinsicHeight); + pw.println(", privateShowing: " + !shouldShowPublic()); + pw.print("privateLayout: "); + if (mPrivateLayout != null) { + pw.println(); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + mPrivateLayout.dump(pw, args); + mPrivateLayout.dumpSmartReplies(pw); + }); + } else { + pw.println("null"); + } + pw.print("publicLayout: "); + if (mPublicLayout != null) { + pw.println(); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + mPublicLayout.dump(pw, args); + }); + } else { + pw.println("null"); + } if (getViewState() != null) { getViewState().dump(pw, args); @@ -3538,31 +3512,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } pw.decreaseIndent(); pw.println("}"); - } else if (mPrivateLayout != null) { - mPrivateLayout.dumpSmartReplies(pw); } }); } - /** - * Background task for executing IPCs to check if the notification is a system notification. The - * output is used for both the blocking helper and the notification info. - */ - private class SystemNotificationAsyncTask extends AsyncTask<Void, Void, Boolean> { - - @Override - protected Boolean doInBackground(Void... voids) { - return isSystemNotification(mContext, mEntry.getSbn()); - } - - @Override - protected void onPostExecute(Boolean result) { - if (mEntry != null) { - mEntry.mIsSystemNotification = result; - } - } - } - private void setTargetPoint(Point p) { mTargetPoint = p; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 599039d46556..a60026c7a97b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -35,6 +35,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.FeedbackIcon; @@ -69,6 +70,7 @@ import javax.inject.Named; public class ExpandableNotificationRowController implements NotifViewController { private static final String TAG = "NotifRowController"; private final ExpandableNotificationRow mView; + private final NotificationLockscreenUserManager mLockscreenUserManager; private final NotificationListContainer mListContainer; private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory; private final ActivatableNotificationViewController mActivatableNotificationViewController; @@ -86,7 +88,6 @@ public class ExpandableNotificationRowController implements NotifViewController private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener; private final StatusBarStateController mStatusBarStateController; private final MetricsLogger mMetricsLogger; - private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener; @@ -100,12 +101,12 @@ public class ExpandableNotificationRowController implements NotifViewController private final Optional<BubblesManager> mBubblesManagerOptional; private final SmartReplyConstants mSmartReplyConstants; private final SmartReplyController mSmartReplyController; - private final ExpandableNotificationRowDragController mDragController; @Inject public ExpandableNotificationRowController( ExpandableNotificationRow view, + NotificationLockscreenUserManager lockscreenUserManager, ActivatableNotificationViewController activatableNotificationViewController, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, MetricsLogger metricsLogger, @@ -135,6 +136,7 @@ public class ExpandableNotificationRowController implements NotifViewController Optional<BubblesManager> bubblesManagerOptional, ExpandableNotificationRowDragController dragController) { mView = view; + mLockscreenUserManager = lockscreenUserManager; mListContainer = listContainer; mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory; mActivatableNotificationViewController = activatableNotificationViewController; @@ -214,6 +216,10 @@ public class ExpandableNotificationRowController implements NotifViewController mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } + mLockscreenUserManager + .addOnNeedsRedactionInPublicChangedListener(mNeedsRedactionListener); + mNeedsRedactionListener.run(); + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -232,6 +238,14 @@ public class ExpandableNotificationRowController implements NotifViewController }); } + private final Runnable mNeedsRedactionListener = new Runnable() { + @Override + public void run() { + mView.setSensitive( + mLockscreenUserManager.notifNeedsRedactionInPublic(mView.getEntry())); + } + }; + private final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override @@ -333,4 +347,5 @@ public class ExpandableNotificationRowController implements NotifViewController public void setFeedbackIcon(@Nullable FeedbackIcon icon) { mView.setFeedbackIcon(icon); } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index ba26cfaa30b4..a7c6bfb0289c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -61,6 +61,7 @@ import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt; import com.android.systemui.statusbar.policy.SmartReplyView; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; +import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.wmshell.BubblesManager; import java.io.PrintWriter; @@ -1994,22 +1995,33 @@ public class NotificationContentView extends FrameLayout implements Notification } } - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); pw.print("contentView visibility: " + getVisibility()); pw.print(", alpha: " + getAlpha()); pw.print(", clipBounds: " + getClipBounds()); pw.print(", contentHeight: " + mContentHeight); - pw.print(", visibleType: " + mVisibleType); - View view = getViewForVisibleType(mVisibleType); - pw.print(", visibleView "); - if (view != null) { - pw.print(" visibility: " + view.getVisibility()); - pw.print(", alpha: " + view.getAlpha()); - pw.print(", clipBounds: " + view.getClipBounds()); - } else { - pw.print("null"); - } - pw.println(); + pw.println(", currentVisibleType: " + mVisibleType); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + int[] visTypes = { + VISIBLE_TYPE_CONTRACTED, + VISIBLE_TYPE_EXPANDED, + VISIBLE_TYPE_HEADSUP, + VISIBLE_TYPE_SINGLELINE + }; + for (int visType : visTypes) { + pw.print("visType: " + visType + " :: "); + View view = getViewForVisibleType(visType); + if (view != null) { + pw.print("visibility: " + view.getVisibility()); + pw.print(", alpha: " + view.getAlpha()); + pw.print(", clipBounds: " + view.getClipBounds()); + } else { + pw.print("null"); + } + pw.println(); + } + }); } /** Add any existing SmartReplyView to the dump */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index f26ecc32821d..a52f638e7c26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -102,8 +102,9 @@ public final class RowContentBindParams { * @see InflationFlag */ public void markContentViewsFreeable(@InflationFlag int contentViews) { + @InflationFlag int existingContentViews = contentViews &= mContentViews; mContentViews &= ~contentViews; - mDirtyContentViews &= ~contentViews; + mDirtyContentViews |= existingContentViews; } public @InflationFlag int getContentViews() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 3ea5e5b753a3..24369e7b860f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -5517,6 +5517,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setFractionToShade(float fraction) { mAmbientState.setFractionToShade(fraction); + updateContentHeight(); // Recompute stack height with different section gap. requestChildrenUpdate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 2493ccbe5a48..6a8f4796813a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -216,6 +216,9 @@ public class NotificationStackScrollLayoutController { mBarState = mStatusBarStateController.getState(); mStatusBarStateController.addCallback( mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER); + mLockscreenUserManager.addOnNeedsRedactionInPublicChangedListener( + mOnNeedsRedactionInPublicChangedListener); + updateClearButtonVisibility(); } @Override @@ -223,6 +226,8 @@ public class NotificationStackScrollLayoutController { mConfigurationController.removeCallback(mConfigurationListener); mZenModeController.removeCallback(mZenModeControllerCallback); mStatusBarStateController.removeCallback(mStateListener); + mLockscreenUserManager.removeOnNeedsRedactionInPublicChangedListener( + mOnNeedsRedactionInPublicChangedListener); } }; @@ -326,6 +331,7 @@ public class NotificationStackScrollLayoutController { mLockscreenUserManager.isAnyProfilePublicMode()); mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); mNotificationEntryManager.updateNotifications("CentralSurfaces state changed"); + updateClearButtonVisibility(); } }; @@ -338,6 +344,17 @@ public class NotificationStackScrollLayoutController { } }; + private final Runnable mOnNeedsRedactionInPublicChangedListener = new Runnable() { + @Override + public void run() { + // Whether or not the notification needs redaction when in public has changed, but if + // we're not actually in public, then we don't need to update anything. + if (mLockscreenUserManager.isAnyProfilePublicMode()) { + updateClearButtonVisibility(); + } + } + }; + /** * Set the overexpansion of the panel to be applied to the view. */ @@ -1274,12 +1291,44 @@ public class NotificationStackScrollLayoutController { return hasNotifications(selection, true /* clearable */); } + private boolean hasRedactedClearableSilentNotifs() { + if (!mLockscreenUserManager.isAnyProfilePublicMode()) { + return false; + } + for (int userId : mNotifStats.getClearableSilentSensitiveNotifUsers()) { + if (mLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(userId)) { + return true; + } + } + return false; + } + + private boolean hasClearableSilentNotifs() { + return mNotifStats.getHasClearableSilentNotifs() && !hasRedactedClearableSilentNotifs(); + } + + private boolean hasRedactedClearableAlertingNotifs() { + if (!mLockscreenUserManager.isAnyProfilePublicMode()) { + return false; + } + for (int userId : mNotifStats.getClearableAlertingSensitiveNotifUsers()) { + if (mLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(userId)) { + return true; + } + } + return false; + } + + private boolean hasClearableAlertingNotifs() { + return mNotifStats.getHasClearableAlertingNotifs() && !hasRedactedClearableAlertingNotifs(); + } + public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { boolean hasAlertingMatchingClearable = isClearable - ? mNotifStats.getHasClearableAlertingNotifs() + ? hasClearableAlertingNotifs() : mNotifStats.getHasNonClearableAlertingNotifs(); boolean hasSilentMatchingClearable = isClearable - ? mNotifStats.getHasClearableSilentNotifs() + ? hasClearableSilentNotifs() : mNotifStats.getHasNonClearableSilentNotifs(); switch (selection) { case ROWS_GENTLE: @@ -1579,6 +1628,15 @@ public class NotificationStackScrollLayoutController { mNotificationActivityStarter = activityStarter; } + private void updateClearButtonVisibility() { + updateClearSilentButton(); + updateFooter(); + } + + private void updateClearSilentButton() { + mSilentHeaderController.setClearSectionEnabled(hasClearableSilentNotifs()); + } + /** * Enum for UiEvent logged from this class */ @@ -1904,6 +1962,7 @@ public class NotificationStackScrollLayoutController { @Override public void setNotifStats(@NonNull NotifStats notifStats) { mNotifStats = notifStats; + updateClearSilentButton(); updateFooter(); updateShowEmptyShadeView(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 2b79986662f1..799fee5e865d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -49,9 +49,10 @@ public class StackScrollAlgorithm { public static final float START_FRACTION = 0.5f; - private static final String LOG_TAG = "StackScrollAlgorithm"; - private final ViewGroup mHostView; + private static final String TAG = "StackScrollAlgorithm"; + private static final Boolean DEBUG = false; + private final ViewGroup mHostView; private int mPaddingBetweenElements; private int mGapHeight; private int mGapHeightOnLockscreen; @@ -126,6 +127,37 @@ public class StackScrollAlgorithm { return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState); } + private void log(String s) { + if (DEBUG) { + android.util.Log.i(TAG, s); + } + } + + public void logView(View view, String s) { + String viewString = ""; + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = ((ExpandableNotificationRow) view); + if (row.getEntry() == null) { + viewString = "ExpandableNotificationRow has null NotificationEntry"; + } else { + viewString = row.getEntry().getSbn().getId() + ""; + } + } else if (view == null) { + viewString = "View is null"; + } else if (view instanceof SectionHeaderView) { + viewString = "SectionHeaderView"; + } else if (view instanceof FooterView) { + viewString = "FooterView"; + } else if (view instanceof MediaContainerView) { + viewString = "MediaContainerView"; + } else if (view instanceof EmptyShadeView) { + viewString = "EmptyShadeView"; + } else { + viewString = view.toString(); + } + log(viewString + " " + s); + } + private void resetChildViewStates() { int numChildren = mHostView.getChildCount(); for (int i = 0; i < numChildren; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 9863a0ed1ce0..f386797e322a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -29,6 +29,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -40,8 +41,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.ViewController; -import java.util.Optional; import java.util.ArrayList; +import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -61,17 +62,17 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar private final NotificationIconAreaController mNotificationIconAreaController; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationStackScrollLayoutController mStackScrollerController; - private final DarkIconDispatcher mDarkIconDispatcher; private final NotificationPanelViewController mNotificationPanelViewController; - private final Consumer<ExpandableNotificationRow> - mSetTrackingHeadsUp = this::setTrackingHeadsUp; + private final Consumer<ExpandableNotificationRow> mSetTrackingHeadsUp = + this::setTrackingHeadsUp; private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction; private final KeyguardBypassController mBypassController; private final StatusBarStateController mStatusBarStateController; private final CommandQueue mCommandQueue; private final NotificationWakeUpCoordinator mWakeUpCoordinator; - + private final NotificationLockscreenUserManager mNotifLockscreenUserManager; + private final Runnable mRedactionChanged = this::updateRedaction; private final View mClockView; private final Optional<View> mOperatorNameViewOptional; @@ -90,6 +91,13 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar }; private boolean mAnimationsEnabled = true; private final KeyguardStateController mKeyguardStateController; + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStatePostChange() { + updateRedaction(); + } + }; @VisibleForTesting @Inject @@ -98,6 +106,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar HeadsUpManagerPhone headsUpManager, StatusBarStateController stateController, KeyguardBypassController bypassController, + NotificationLockscreenUserManager notifLockscreenUserManager, NotificationWakeUpCoordinator wakeUpCoordinator, DarkIconDispatcher darkIconDispatcher, KeyguardStateController keyguardStateController, @@ -125,6 +134,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mClockView = clockView; mOperatorNameViewOptional = operatorNameViewOptional; mDarkIconDispatcher = darkIconDispatcher; + mNotifLockscreenUserManager = notifLockscreenUserManager; mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override @@ -156,6 +166,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mNotificationPanelViewController.setHeadsUpAppearanceController(this); mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight); mDarkIconDispatcher.addDarkReceiver(this); + mNotifLockscreenUserManager.addOnNeedsRedactionInPublicChangedListener(mRedactionChanged); + mStatusBarStateController.addCallback(mStatusBarStateListener); } @Override @@ -167,6 +179,9 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mNotificationPanelViewController.setHeadsUpAppearanceController(null); mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight); mDarkIconDispatcher.removeDarkReceiver(this); + mNotifLockscreenUserManager + .removeOnNeedsRedactionInPublicChangedListener(mRedactionChanged); + mStatusBarStateController.removeCallback(mStatusBarStateListener); } private void updateIsolatedIconLocation(boolean requireStateUpdate) { @@ -180,6 +195,19 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar updateHeader(entry); } + private void updateRedaction() { + NotificationEntry showingEntry = mView.getShowingEntry(); + if (showingEntry == null) { + return; + } + int notifUserId = showingEntry.getSbn().getUserId(); + boolean redactSensitiveContent = + mNotifLockscreenUserManager.isLockscreenPublicMode(notifUserId) + && mNotifLockscreenUserManager + .sensitiveNotifsNeedRedactionInPublic(notifUserId); + mView.setRedactSensitiveContent(redactSensitiveContent); + } + private void updateTopEntry() { NotificationEntry newEntry = null; if (shouldBeVisible()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 0b721383e2d1..8792118fb9ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -188,6 +188,10 @@ public class KeyguardBouncer { } if (mContainer.getVisibility() == View.VISIBLE || mShowingSoon) { + // Calls to reset must resume the ViewControllers when in fullscreen mode + if (needsFullscreenBouncer()) { + mKeyguardViewController.onResume(); + } return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 74b9c71d2198..f15ea627af47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -32,7 +32,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN; @@ -1690,7 +1689,17 @@ public class NotificationPanelViewController extends PanelViewController { mQsExpandImmediate = true; setShowShelfOnly(true); } - if (isFullyCollapsed()) { + if (mShouldUseSplitNotificationShade && isOnKeyguard()) { + // It's a special case as this method is likely to not be initiated by finger movement + // but rather called from adb shell or accessibility service. + // We're using LockscreenShadeTransitionController because on lockscreen that's the + // source of truth for all shade motion. Not using it would make part of state to be + // outdated and will cause bugs. Ideally we'd use this controller also for non-split + // case but currently motion in portrait looks worse than when using flingSettings. + // TODO: make below function transitioning smoothly also in portrait with null target + mLockscreenShadeTransitionController.goToLockedShade( + /* expandedView= */null, /* needsQSAnimation= */false); + } else if (isFullyCollapsed()) { expand(true /* animate */); } else { traceQsJank(true /* startTracing */, false /* wasCancelled */); @@ -2355,9 +2364,12 @@ public class NotificationPanelViewController extends PanelViewController { mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); setQSClippingBounds(); - // Only need to notify the notification stack when we're not in split screen mode. If we - // do, then the notification panel starts scrolling along with the QS. - if (!mShouldUseSplitNotificationShade) { + if (mShouldUseSplitNotificationShade) { + // In split shade we want to pretend that QS are always collapsed so their behaviour and + // interactions don't influence notifications as they do in portrait. But we want to set + // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1. + mNotificationStackScrollLayoutController.setQsExpansionFraction(0); + } else { mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); } @@ -3210,6 +3222,8 @@ public class NotificationPanelViewController extends PanelViewController { updateTrackingHeadsUp(null); mExpandingFromHeadsUp = false; setPanelScrimMinFraction(0.0f); + // Reset status bar alpha so alpha can be calculated upon updating view state. + setKeyguardStatusBarAlpha(-1f); } private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) { @@ -3929,10 +3943,6 @@ public class NotificationPanelViewController extends PanelViewController { } } - public boolean hasActiveClearableNotifications() { - return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL); - } - public RemoteInputController.Delegate createRemoteInputDelegate() { return mNotificationStackScrollLayoutController.createDelegate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index fb8397ba8f4f..7aeb08dd5ddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -53,6 +53,8 @@ public class PhoneStatusBarView extends FrameLayout { private View mCutoutSpace; @Nullable private DisplayCutout mDisplayCutout; + @Nullable + private Rect mDisplaySize; private int mStatusBarHeight; @Nullable private TouchEventHandler mTouchEventHandler; @@ -87,7 +89,7 @@ public class PhoneStatusBarView extends FrameLayout { // Always have Battery meters in the status bar observe the dark/light modes. Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock); - if (updateOrientationAndCutout()) { + if (updateDisplayParameters()) { updateLayoutForCutout(); } } @@ -106,7 +108,7 @@ public class PhoneStatusBarView extends FrameLayout { updateResources(); // May trigger cutout space layout-ing - if (updateOrientationAndCutout()) { + if (updateDisplayParameters()) { updateLayoutForCutout(); requestLayout(); } @@ -114,7 +116,7 @@ public class PhoneStatusBarView extends FrameLayout { @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (updateOrientationAndCutout()) { + if (updateDisplayParameters()) { updateLayoutForCutout(); requestLayout(); } @@ -124,7 +126,7 @@ public class PhoneStatusBarView extends FrameLayout { /** * @return boolean indicating if we need to update the cutout location / margins */ - private boolean updateOrientationAndCutout() { + private boolean updateDisplayParameters() { boolean changed = false; int newRotation = RotationUtils.getExactRotation(mContext); if (newRotation != mRotationOrientation) { @@ -137,6 +139,13 @@ public class PhoneStatusBarView extends FrameLayout { mDisplayCutout = getRootWindowInsets().getDisplayCutout(); } + final Rect newSize = mContext.getResources().getConfiguration().windowConfiguration + .getMaxBounds(); + if (!Objects.equals(newSize, mDisplaySize)) { + changed = true; + mDisplaySize = newSize; + } + return changed; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index 67de4e3b3c39..f5462bc0fba5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -38,6 +38,7 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation import com.android.systemui.util.leak.RotationUtils.getExactRotation import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation +import com.android.systemui.util.traceSection import java.io.PrintWriter import java.lang.Math.max @@ -133,8 +134,10 @@ class StatusBarContentInsetsProvider @Inject constructor( * (i.e., ROTATION_NONE will always return the same bounds regardless of the context * from which this method is called) */ - fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int): Rect { - var insets = insetsCache[getCacheKey(rotation = rotation)] + fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int, + displayCutout: DisplayCutout?): Rect { + val key = getCacheKey(rotation, displayCutout) + var insets = insetsCache[key] if (insets == null) { insets = getStatusBarContentAreaForRotation(rotation) } @@ -156,20 +159,23 @@ class StatusBarContentInsetsProvider @Inject constructor( * * @param rotation the target rotation for which to calculate insets */ - fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> { - val key = getCacheKey(rotation) + fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> = + traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") { + val displayCutout = context.display.cutout + val key = getCacheKey(rotation, displayCutout) - val point = Point() - context.display.getRealSize(point) - // Target rotation can be a different orientation than the current device rotation - point.orientToRotZero(getExactRotation(context)) - val width = point.logicalWidth(rotation) + val screenBounds = context.resources.configuration.windowConfiguration.maxBounds + val point = Point(screenBounds.width(), screenBounds.height()) - val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation( - rotation, getResourcesForRotation(rotation, context), key) + // Target rotation can be a different orientation than the current device rotation + point.orientToRotZero(getExactRotation(context)) + val width = point.logicalWidth(rotation) - return Pair(area.left, width - area.right) - } + val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation( + rotation, displayCutout, getResourcesForRotation(rotation, context), key) + + Pair(area.left, width - area.right) + } /** * Calculate the left and right insets for the status bar content in the device's current @@ -192,9 +198,10 @@ class StatusBarContentInsetsProvider @Inject constructor( fun getStatusBarContentAreaForRotation( @Rotation rotation: Int ): Rect { - val key = getCacheKey(rotation) + val displayCutout = context.display.cutout + val key = getCacheKey(rotation, displayCutout) return insetsCache[key] ?: getAndSetCalculatedAreaForRotation( - rotation, getResourcesForRotation(rotation, context), key) + rotation, displayCutout, getResourcesForRotation(rotation, context), key) } /** @@ -207,20 +214,21 @@ class StatusBarContentInsetsProvider @Inject constructor( private fun getAndSetCalculatedAreaForRotation( @Rotation targetRotation: Int, + displayCutout: DisplayCutout?, rotatedResources: Resources, key: CacheKey ): Rect { - return getCalculatedAreaForRotation(targetRotation, rotatedResources) + return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources) .also { insetsCache.put(key, it) } } private fun getCalculatedAreaForRotation( + displayCutout: DisplayCutout?, @Rotation targetRotation: Int, rotatedResources: Resources ): Rect { - val dc = context.display.cutout val currentRotation = getExactRotation(context) val roundedCornerPadding = rotatedResources @@ -245,7 +253,7 @@ class StatusBarContentInsetsProvider @Inject constructor( return calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, - dc, + displayCutout, context.resources.configuration.windowConfiguration.maxBounds, SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation), minLeft, @@ -266,15 +274,19 @@ class StatusBarContentInsetsProvider @Inject constructor( pw.println(insetsCache) } - private fun getCacheKey(@Rotation rotation: Int): CacheKey = + private fun getCacheKey( + @Rotation rotation: Int, + displayCutout: DisplayCutout?): CacheKey = CacheKey( - uniqueDisplayId = context.display.uniqueId, - rotation = rotation + rotation = rotation, + displaySize = Rect(context.resources.configuration.windowConfiguration.maxBounds), + displayCutout = displayCutout ) private data class CacheKey( - val uniqueDisplayId: String, - @Rotation val rotation: Int + @Rotation val rotation: Int, + val displaySize: Rect, + val displayCutout: DisplayCutout? ) } @@ -369,7 +381,7 @@ fun calculateInsetsForRotationWithRotatedResources( /** * Calculate the insets needed from the left and right edges for the given rotation. * - * @param dc Device display cutout + * @param displayCutout Device display cutout * @param sbHeight appropriate status bar height for this rotation * @param width display width calculated for ROTATION_NONE * @param height display height calculated for ROTATION_NONE @@ -386,7 +398,7 @@ fun calculateInsetsForRotationWithRotatedResources( * rotation */ private fun getStatusBarLeftRight( - dc: DisplayCutout?, + displayCutout: DisplayCutout?, sbHeight: Int, width: Int, height: Int, @@ -402,7 +414,7 @@ private fun getStatusBarLeftRight( val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width - val cutoutRects = dc?.boundingRects + val cutoutRects = displayCutout?.boundingRects if (cutoutRects == null || cutoutRects.isEmpty()) { return Rect(minLeft, 0, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index aa061d74f6c6..037063f4f7b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -411,8 +411,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, if (nowExpanded) { if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); - } else if (clickedEntry.isSensitive() - && mDynamicPrivacyController.isInLockedDownShade()) { + } else if (mDynamicPrivacyController.isInLockedDownShade() + && mLockscreenUserManager.notifNeedsRedactionInPublic(clickedEntry)) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */ , null /* cancelRunnable */, false /* afterKeyguardGone */); @@ -480,7 +480,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); boolean userPublic = devicePublic || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); - boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); + boolean needsRedaction = mLockscreenUserManager.notifNeedsRedactionInPublic(entry); if (userPublic && needsRedaction) { // TODO(b/135046837): we can probably relax this with dynamic privacy return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index c1d0769eaa44..b11751554db3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -311,9 +311,9 @@ class UnlockedScreenOffAnimationController @Inject constructor( // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's // already expanded and showing notifications/QS, the animation looks really messy. For now, - // disable it if the notification panel is not fully collapsed. + // disable it if the notification panel is expanded. if ((!this::mCentralSurfaces.isInitialized || - !mCentralSurfaces.notificationPanelViewController.isFullyCollapsed) && + mCentralSurfaces.notificationPanelViewController.isExpanded) && // Status bar might be expanded because we have started // playing the animation already !isAnimationPlaying() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index 15ee553da457..dce24122aa7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy; import android.app.IActivityTaskManager; +import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.KeyguardStateController.Callback; @@ -94,6 +95,15 @@ public interface KeyguardStateController extends CallbackController<Callback> { boolean isKeyguardGoingAway(); /** + * Whether we're currently animating between the keyguard and the app/launcher surface behind + * it, or will be shortly (which happens if we started a fling to dismiss the keyguard). + * @see {@link KeyguardViewMediator#isAnimatingBetweenKeyguardAndSurfaceBehind()} + */ + default boolean isAnimatingBetweenKeyguardAndSurfaceBehind() { + return false; + }; + + /** * @return a shortened fading away duration similar to * {{@link #getKeyguardFadingAwayDuration()}} which may only span half of the duration, unless * we're bypassing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 77e285d1e15d..2a225b909f90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -279,6 +279,11 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override + public boolean isAnimatingBetweenKeyguardAndSurfaceBehind() { + return mUnlockAnimationControllerLazy.get().isAnimatingBetweenKeyguardAndSurfaceBehind(); + } + + @Override public boolean isBypassFadingAnimation() { return mBypassFadingAnimation; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index c53d5107afcc..e0d780a5fcd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -31,7 +31,9 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; import android.os.RemoteException; +import android.os.Trace; import android.util.Log; +import android.view.DisplayCutout; import android.view.Gravity; import android.view.IWindowManager; import android.view.Surface; @@ -130,7 +132,9 @@ public class StatusBarWindowController { // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. + Trace.beginSection("StatusBarWindowController.getBarLayoutParams"); mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); + Trace.endSection(); mWindowManager.addView(mStatusBarWindowView, mLp); mLpChanged.copyFrom(mLp); @@ -223,14 +227,15 @@ public class StatusBarWindowController { private void calculateStatusBarLocationsForAllRotations() { Rect[] bounds = new Rect[4]; + final DisplayCutout displayCutout = mContext.getDisplay().getCutout(); bounds[0] = mContentInsetsProvider - .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE); + .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE, displayCutout); bounds[1] = mContentInsetsProvider - .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE); + .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE, displayCutout); bounds[2] = mContentInsetsProvider - .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN); + .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN, displayCutout); bounds[3] = mContentInsetsProvider - .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE); + .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE, displayCutout); try { mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds); diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java b/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java index 2b7a33260248..90f24347d20d 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvBottomSheetActivity.java @@ -18,15 +18,18 @@ package com.android.systemui.tv; import android.app.Activity; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; +import android.view.View; import android.view.WindowManager; import com.android.systemui.R; +import java.util.Collections; import java.util.function.Consumer; /** @@ -75,6 +78,12 @@ public abstract class TvBottomSheetActivity extends Activity { getWindow().setElevation(getWindow().getElevation() + 5); getWindow().setBackgroundBlurRadius(getResources().getDimensionPixelSize( R.dimen.bottom_sheet_background_blur_radius)); + + final View rootView = findViewById(R.id.bottom_sheet); + rootView.addOnLayoutChangeListener((view, l, t, r, b, oldL, oldT, oldR, oldB) -> { + rootView.setUnrestrictedPreferKeepClearRects( + Collections.singletonList(new Rect(0, 0, r - l, b - t))); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 3329eabc80ad..ad734914170b 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -71,6 +71,11 @@ class UserSwitcherActivity @Inject constructor( private var popupMenu: UserSwitcherPopupMenu? = null private lateinit var addButton: View private var addUserRecords = mutableListOf<UserRecord>() + private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + finish() + } + } // When the add users options become available, insert another option to manage users private val manageUserRecord = UserRecord( null /* info */, @@ -215,11 +220,7 @@ class UserSwitcherActivity @Inject constructor( initBroadcastReceiver() parent.post { buildUserViews() } - userTracker.addCallback(object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - finish() - } - }, mainExecutor) + userTracker.addCallback(userSwitchedCallback, mainExecutor) } private fun showPopupMenu() { @@ -340,6 +341,7 @@ class UserSwitcherActivity @Inject constructor( super.onDestroy() broadcastDispatcher.unregisterReceiver(broadcastReceiver) + userTracker.removeCallback(userSwitchedCallback) } private fun initBroadcastReceiver() { diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index ebdddbf66091..acff8712e92e 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -180,7 +180,7 @@ public class QuickAccessWalletController { int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); GetWalletCardsRequest request = new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1); - mQuickAccessWalletClient.getWalletCards(mCallbackExecutor, request, cardsRetriever); + mQuickAccessWalletClient.getWalletCards(mExecutor, request, cardsRetriever); } /** diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index cddfd4498cfb..a6db2aa48a88 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -200,9 +200,13 @@ public final class WMShell extends CoreStartable mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() { @Override public void onKeyguardVisibilityChanged(boolean showing) { - if (showing) { - pip.hidePipMenu(null, null); - } + pip.onKeyguardVisibilityChanged(showing, + mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind()); + } + + @Override + public void onKeyguardDismissAnimationFinished() { + pip.onKeyguardDismissAnimationFinished(); } }; mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 7c211b22e657..57253af57f0e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -159,6 +159,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { when(mContext.getDisplay()).thenReturn(mDisplay); // Not support hwc layer by default doReturn(null).when(mDisplay).getDisplayDecorationSupport(); + doReturn(mDisplayMode).when(mDisplay).getMode(); when(mMockTypedArray.length()).thenReturn(0); mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt new file mode 100644 index 000000000000..a52c4a3978b4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.app.Instrumentation +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.ViewUtils +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.StatusBarStateControllerImpl +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.util.mockito.any +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class UdfpsBpViewControllerTest : SysuiTestCase() { + + @JvmField @Rule var rule = MockitoJUnit.rule() + + @Mock lateinit var dumpManager: DumpManager + @Mock lateinit var systemUIDialogManager: SystemUIDialogManager + @Mock lateinit var broadcastSender: BroadcastSender + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var panelExpansionStateManager: PanelExpansionStateManager + + private lateinit var instrumentation: Instrumentation + private lateinit var uiEventLogger: UiEventLoggerFake + private lateinit var udfpsBpView: UdfpsBpView + private lateinit var statusBarStateController: StatusBarStateControllerImpl + private lateinit var udfpsBpViewController: UdfpsBpViewController + + @Before + fun setup() { + instrumentation = getInstrumentation() + instrumentation.runOnMainSync { createUdfpsView() } + instrumentation.waitForIdleSync() + + uiEventLogger = UiEventLoggerFake() + statusBarStateController = + StatusBarStateControllerImpl(uiEventLogger, dumpManager, interactionJankMonitor) + udfpsBpViewController = UdfpsBpViewController( + udfpsBpView, + statusBarStateController, + panelExpansionStateManager, + systemUIDialogManager, + broadcastSender, + dumpManager) + udfpsBpViewController.init() + } + + @After + fun tearDown() { + if (udfpsBpViewController.isAttachedToWindow) { + instrumentation.runOnMainSync { ViewUtils.detachView(udfpsBpView) } + instrumentation.waitForIdleSync() + } + } + + private fun createUdfpsView() { + context.setTheme(R.style.Theme_AppCompat) + context.orCreateTestableResources.addOverride( + com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0) + udfpsBpView = UdfpsBpView(context, null) + } + + @Test + fun addExpansionListener() { + instrumentation.runOnMainSync { ViewUtils.attachView(udfpsBpView) } + instrumentation.waitForIdleSync() + + // Both UdfpsBpViewController & UdfpsAnimationViewController add listener + verify(panelExpansionStateManager, times(2)).addExpansionListener(any()) + } + + @Test + fun removeExpansionListener() { + instrumentation.runOnMainSync { ViewUtils.attachView(udfpsBpView) } + instrumentation.waitForIdleSync() + instrumentation.runOnMainSync { ViewUtils.detachView(udfpsBpView) } + instrumentation.waitForIdleSync() + + // Both UdfpsBpViewController & UdfpsAnimationViewController remove listener + verify(panelExpansionStateManager, times(2)).removeExpansionListener(any()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index fc5ccbcb4a76..6eba2154e46c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -40,6 +40,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -102,6 +103,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsView: UdfpsView @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator + @Mock private lateinit var broadcastSender: BroadcastSender @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -131,7 +133,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, configurationController, systemClock, keyguardStateController, unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider, - REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator) + REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator, + broadcastSender) block() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 87d3cd0cf0a0..290965c33509 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -66,6 +66,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; @@ -186,6 +187,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private AlternateUdfpsTouchProvider mAlternateTouchProvider; + @Mock + private BroadcastSender mBroadcastSender; // Capture listeners so that they can be used to send events @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor; @@ -261,7 +264,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, - Optional.of(mAlternateTouchProvider)); + Optional.of(mAlternateTouchProvider), + mBroadcastSender); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index b87a7e78f73a..0fdd9054e4bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -168,7 +168,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mController.onViewAttached(); verify(mView, atLeast(1)).setPauseAuth(true); - verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount, true); + verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount, + UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN); } @Test @@ -195,7 +196,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { final float eased = .65f; mStatusBarStateListener.onDozeAmountChanged(linear, eased); - verify(mView).onDozeAmountChanged(linear, eased, true); + verify(mView).onDozeAmountChanged(linear, eased, + UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN); } @Test 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 de04d3e9d059..91214a85ddd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -19,6 +19,8 @@ package com.android.systemui.clipboardoverlay; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -26,7 +28,9 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; +import android.os.PersistableBundle; import android.provider.DeviceConfig; import androidx.test.filters.SmallTest; @@ -139,4 +143,30 @@ public class ClipboardListenerTest extends SysuiTestCase { verify(mClipboardOverlayControllerFactory, times(2)).create(any()); } + + @Test + public void test_shouldSuppressOverlay() { + // Regardless of the package or emulator, nothing should be suppressed without the flag + assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource, + false)); + assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, + ClipboardListener.SHELL_PACKAGE, false)); + assertFalse(ClipboardListener.shouldSuppressOverlay(mSampleClipData, mSampleSource, + true)); + + ClipDescription desc = new ClipDescription("Test", new String[]{"text/plain"}); + PersistableBundle bundle = new PersistableBundle(); + bundle.putBoolean(ClipboardListener.EXTRA_SUPPRESS_OVERLAY, true); + desc.setExtras(bundle); + ClipData suppressableClipData = new ClipData(desc, new ClipData.Item("Test Item")); + + // Clip data with the suppression extra is only honored in the emulator or with the shell + // package. + assertFalse(ClipboardListener.shouldSuppressOverlay(suppressableClipData, mSampleSource, + false)); + assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData, mSampleSource, + true)); + assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData, + ClipboardListener.SHELL_PACKAGE, false)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 0ed579f9c10b..241ed2443e6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorSet import android.app.PendingIntent import android.app.smartspace.SmartspaceAction import android.content.Context +import org.mockito.Mockito.`when` as whenever import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager @@ -58,6 +59,7 @@ import com.android.systemui.ActivityIntentHelper import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME import com.android.systemui.media.dialog.MediaOutputDialogFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -66,8 +68,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.KotlinArgumentCaptor -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor @@ -91,7 +93,6 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -import org.mockito.Mockito.`when` as whenever private const val KEY = "TEST_KEY" private const val PACKAGE = "PKG" @@ -102,6 +103,7 @@ private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" private const val SESSION_TITLE = "SESSION_TITLE" private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME" +private const val REC_APP_NAME = "REC APP NAME" @SmallTest @RunWith(AndroidTestingRunner::class) @@ -262,6 +264,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Set valid recommendation data val extras = Bundle() + extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) val intent = Intent().apply { putExtras(extras) setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -339,7 +342,6 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) whenever(viewHolder.albumView).thenReturn(albumView) - whenever(albumView.foreground).thenReturn(mock(Drawable::class.java)) whenever(viewHolder.titleText).thenReturn(titleText) whenever(viewHolder.artistText).thenReturn(artistText) whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) @@ -1122,6 +1124,91 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false)) } + @Test + fun player_gutsOpen_contentDescriptionIsForGuts() { + whenever(mediaViewController.isGutsVisible).thenReturn(true) + player.attachPlayer(viewHolder) + + val gutsTextString = "gutsText" + whenever(gutsText.text).thenReturn(gutsTextString) + player.bindPlayer(mediaData, KEY) + + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).isEqualTo(gutsTextString) + } + + @Test + fun player_gutsClosed_contentDescriptionIsForPlayer() { + whenever(mediaViewController.isGutsVisible).thenReturn(false) + player.attachPlayer(viewHolder) + + val app = "appName" + player.bindPlayer(mediaData.copy(app = app), KEY) + + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).contains(mediaData.song!!) + assertThat(description).contains(mediaData.artist!!) + assertThat(description).contains(app) + } + + @Test + fun player_gutsChangesFromOpenToClosed_contentDescriptionUpdated() { + // Start out open + whenever(mediaViewController.isGutsVisible).thenReturn(true) + whenever(gutsText.text).thenReturn("gutsText") + player.attachPlayer(viewHolder) + val app = "appName" + player.bindPlayer(mediaData.copy(app = app), KEY) + + // Update to closed by long pressing + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(viewHolder.player).onLongClickListener = captor.capture() + reset(viewHolder.player) + + whenever(mediaViewController.isGutsVisible).thenReturn(false) + captor.value.onLongClick(viewHolder.player) + + // Then content description is now the player content description + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).contains(mediaData.song!!) + assertThat(description).contains(mediaData.artist!!) + assertThat(description).contains(app) + } + + @Test + fun player_gutsChangesFromClosedToOpen_contentDescriptionUpdated() { + // Start out closed + whenever(mediaViewController.isGutsVisible).thenReturn(false) + val gutsTextString = "gutsText" + whenever(gutsText.text).thenReturn(gutsTextString) + player.attachPlayer(viewHolder) + player.bindPlayer(mediaData.copy(app = "appName"), KEY) + + // Update to open by long pressing + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(viewHolder.player).onLongClickListener = captor.capture() + reset(viewHolder.player) + + whenever(mediaViewController.isGutsVisible).thenReturn(true) + captor.value.onLongClick(viewHolder.player) + + // Then content description is now the guts content description + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).isEqualTo(gutsTextString) + } + /* ***** END guts tests for the player ***** */ /* ***** Guts tests for the recommendations ***** */ @@ -1190,6 +1277,85 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong()) } + @Test + fun recommendation_gutsOpen_contentDescriptionIsForGuts() { + whenever(mediaViewController.isGutsVisible).thenReturn(true) + player.attachRecommendation(recommendationViewHolder) + + val gutsTextString = "gutsText" + whenever(gutsText.text).thenReturn(gutsTextString) + player.bindRecommendation(smartspaceData) + + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).isEqualTo(gutsTextString) + } + + @Test + fun recommendation_gutsClosed_contentDescriptionIsForPlayer() { + whenever(mediaViewController.isGutsVisible).thenReturn(false) + player.attachRecommendation(recommendationViewHolder) + + player.bindRecommendation(smartspaceData) + + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).contains(REC_APP_NAME) + } + + @Test + fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() { + // Start out open + whenever(mediaViewController.isGutsVisible).thenReturn(true) + whenever(gutsText.text).thenReturn("gutsText") + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + // Update to closed by long pressing + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(viewHolder.player).onLongClickListener = captor.capture() + reset(viewHolder.player) + + whenever(mediaViewController.isGutsVisible).thenReturn(false) + captor.value.onLongClick(viewHolder.player) + + // Then content description is now the player content description + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).contains(REC_APP_NAME) + } + + @Test + fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() { + // Start out closed + whenever(mediaViewController.isGutsVisible).thenReturn(false) + val gutsTextString = "gutsText" + whenever(gutsText.text).thenReturn(gutsTextString) + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + // Update to open by long pressing + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(viewHolder.player).onLongClickListener = captor.capture() + reset(viewHolder.player) + + whenever(mediaViewController.isGutsVisible).thenReturn(true) + captor.value.onLongClick(viewHolder.player) + + // Then content description is now the guts content description + val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) + verify(viewHolder.player).contentDescription = descriptionCaptor.capture() + val description = descriptionCaptor.value.toString() + + assertThat(description).isEqualTo(gutsTextString) + } + /* ***** END guts tests for the recommendations ***** */ @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 10eeb11faa05..18ee79138b52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -25,19 +25,17 @@ import android.media.session.MediaSession import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest - import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock - import com.google.common.truth.Truth.assertThat - import org.junit.After import org.junit.Before import org.junit.Rule @@ -50,8 +48,8 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever private const val KEY = "TEST_KEY" private const val KEY_OLD = "TEST_KEY_OLD" @@ -81,6 +79,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var route: RoutingSessionInfo @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo + @Mock private lateinit var configurationController: ConfigurationController private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -94,6 +93,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { lmmFactory, mr2, muteAwaitFactory, + configurationController, fakeFgExecutor, fakeBgExecutor, dumpster diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt index a07447521ab8..dafaa6b93696 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt @@ -35,6 +35,7 @@ import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -73,6 +74,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { @Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback> @Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback> + @Captor lateinit var mediaControllerCallback: ArgumentCaptor<MediaController.Callback> @Before fun setUp() { @@ -82,6 +84,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { .thenReturn(browser) whenever(mediaController.transportControls).thenReturn(transportControls) + whenever(mediaController.sessionToken).thenReturn(token) resumeBrowser = TestableResumeMediaBrowser( context, @@ -137,6 +140,22 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } @Test + fun testConnection_thenSessionDestroyed_disconnects() { + // When testConnection is called and we connect successfully + setupBrowserConnection() + resumeBrowser.testConnection() + verify(mediaController).registerCallback(mediaControllerCallback.capture()) + reset(browser) + + // And a sessionDestroyed event is triggered + mediaControllerCallback.value.onSessionDestroyed() + + // Then we disconnect the browser and unregister the callback + verify(browser).disconnect() + verify(mediaController).unregisterCallback(mediaControllerCallback.value) + } + + @Test fun testConnection_calledTwice_oldBrowserDisconnected() { val oldBrowser = mock<MediaBrowser>() whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser) @@ -188,6 +207,22 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } @Test + fun testFindRecentMedia_thenSessionDestroyed_disconnects() { + // When findRecentMedia is called and we connect successfully + setupBrowserConnection() + resumeBrowser.findRecentMedia() + verify(mediaController).registerCallback(mediaControllerCallback.capture()) + reset(browser) + + // And a sessionDestroyed event is triggered + mediaControllerCallback.value.onSessionDestroyed() + + // Then we disconnect the browser and unregister the callback + verify(browser).disconnect() + verify(mediaController).unregisterCallback(mediaControllerCallback.value) + } + + @Test fun testFindRecentMedia_calledTwice_oldBrowserDisconnected() { val oldBrowser = mock<MediaBrowser>() whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser) @@ -261,6 +296,22 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } @Test + fun testRestart_thenSessionDestroyed_disconnects() { + // When restart is called and we connect successfully + setupBrowserConnection() + resumeBrowser.restart() + verify(mediaController).registerCallback(mediaControllerCallback.capture()) + reset(browser) + + // And a sessionDestroyed event is triggered + mediaControllerCallback.value.onSessionDestroyed() + + // Then we disconnect the browser and unregister the callback + verify(browser).disconnect() + verify(mediaController).unregisterCallback(mediaControllerCallback.value) + } + + @Test fun testRestart_calledTwice_oldBrowserDisconnected() { val oldBrowser = mock<MediaBrowser>() whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index a52608733ca4..1cfa3b2a08ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -47,6 +47,7 @@ import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; +import android.content.res.Resources; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.SystemClock; @@ -102,6 +103,8 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxyFake; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.utils.leaks.LeakCheckedTest; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; @@ -126,10 +129,10 @@ public class NavigationBarTest extends SysuiTestCase { private SysuiTestableContext mSysuiTestableContextExternal; @Mock - NavigationBarFrame mNavigationBarFrame; - @Mock NavigationBarView mNavigationBarView; @Mock + NavigationBarFrame mNavigationBarFrame; + @Mock ButtonDispatcher mHomeButton; @Mock ButtonDispatcher mRecentsButton; @@ -191,6 +194,9 @@ public class NavigationBarTest extends SysuiTestCase { private CentralSurfaces mCentralSurfaces; @Mock private UserContextProvider mUserContextProvider; + @Mock + private Resources mResources; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); @Rule @@ -215,6 +221,7 @@ public class NavigationBarTest extends SysuiTestCase { when(mNavigationBarView.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mUserContextProvider.createCurrentUserContext(any(Context.class))) .thenReturn(mContext); + when(mNavigationBarView.getResources()).thenReturn(mResources); setupSysuiDependency(); // This class inflates views that call Dependency.get, thus these injections are still // necessary. @@ -450,6 +457,8 @@ public class NavigationBarTest extends SysuiTestCase { mock(NotificationRemoteInputManager.class), mock(NotificationShadeDepthController.class), mHandler, + mFakeExecutor, + mFakeExecutor, mUiEventLogger, mNavBarHelper, mLightBarController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 4a6bbbcf1d6b..346d1e60fcf9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -24,7 +24,6 @@ import static com.android.systemui.qrcodescanner.controller.QRCodeScannerControl import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +31,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; @@ -41,7 +41,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.DeviceConfigProxyFake; @@ -90,8 +89,9 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { when(mPackageManager.queryIntentActivities(any(Intent.class), any(Integer.class))).thenReturn(resolveInfoList); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(true); - mContext.getOrCreateTestableResources().addOverride(R.string.def_qr_code_component, - defaultActivity); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.string.config_defaultQrCodeComponent, defaultActivity); + mContext.getOrCreateTestableResources().addOverride( android.R.bool.config_enableQrCodeScannerOnLockScreen, enableOnLockScreen); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java new file mode 100644 index 000000000000..b86713d0890b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.os.Handler; +import android.service.quicksettings.Tile; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.logging.MetricsLogger; +import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.statusbar.policy.DataSaverController; +import com.android.systemui.statusbar.policy.HotspotController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class HotspotTileTest extends SysuiTestCase { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + @Mock + private QSTileHost mHost; + @Mock + private HotspotController mHotspotController; + @Mock + private DataSaverController mDataSaverController; + + private TestableLooper mTestableLooper; + private HotspotTile mTile; + private QSTile.BooleanState mState = new QSTile.BooleanState(); + + @Before + public void setUp() throws Exception { + mTestableLooper = TestableLooper.get(this); + when(mHost.getContext()).thenReturn(mContext); + when(mHost.getUserContext()).thenReturn(mContext); + when(mDataSaverController.isDataSaverEnabled()).thenReturn(false); + + mTile = new HotspotTile( + mHost, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mock(MetricsLogger.class), + mock(StatusBarStateController.class), + mock(ActivityStarter.class), + mock(QSLogger.class), + mHotspotController, + mDataSaverController + ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); + } + + @Test + public void handleUpdateState_wifiTetheringIsAllowed_stateIsNotUnavailable() { + MockitoSession mockitoSession = ExtendedMockito.mockitoSession() + .spyStatic(WifiEnterpriseRestrictionUtils.class) + .startMocking(); + when(WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mContext)).thenReturn(true); + + mTile.handleUpdateState(mState, null); + + assertThat(mState.state).isNotEqualTo(Tile.STATE_UNAVAILABLE); + assertThat(String.valueOf(mState.secondaryLabel)) + .isNotEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network)); + mockitoSession.finishMocking(); + } + + @Test + public void handleUpdateState_wifiTetheringIsDisallowed_stateIsUnavailable() { + MockitoSession mockitoSession = ExtendedMockito.mockitoSession() + .spyStatic(WifiEnterpriseRestrictionUtils.class) + .startMocking(); + when(WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mContext)).thenReturn(false); + + mTile.handleUpdateState(mState, null); + + assertThat(mState.state).isEqualTo(Tile.STATE_UNAVAILABLE); + assertThat(String.valueOf(mState.secondaryLabel)) + .isEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network)); + mockitoSession.finishMocking(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 562c97017862..73e574eddd17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -130,8 +130,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { whenever(statusbarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(nsslController.isInLockedDownShade).thenReturn(false) whenever(qS.isFullyCollapsed).thenReturn(true) - whenever(lockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn( - true) + whenever(lockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(anyInt())) + .thenReturn(false) whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true) whenever(lockScreenUserManager.isLockscreenPublicMode(anyInt())).thenReturn(true) whenever(falsingCollector.shouldEnforceBouncer()).thenReturn(false) @@ -207,8 +207,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Test fun testTriggeringBouncerWhenPrivateNotificationsArentAllowed() { - whenever(lockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn( - false) + whenever(lockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(anyInt())) + .thenReturn(true) transitionController.goToLockedShade(null) verify(statusbarStateController, never()).setState(anyInt()) verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 7687d1204541..18937e784ed1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -53,11 +53,13 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.NotificationLockscreenUserManager.KeyguardNotificationSuppressor; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -106,6 +108,10 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private KeyguardStateController mKeyguardStateController; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private OverviewProxyService mOverviewProxyService; private UserInfo mCurrentUser; private UserInfo mSecondaryUser; @@ -119,7 +125,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); int currentUserId = ActivityManager.getCurrentUser(); mSettings = new FakeSettings(); @@ -212,7 +217,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // THEN current user's notification is redacted - assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif)); } @Test @@ -223,7 +228,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // THEN current user's notification isn't redacted - assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif)); } @Test @@ -234,7 +239,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // THEN work profile notification is redacted - assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif)); } @Test @@ -245,7 +250,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // THEN work profile notification isn't redacted - assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif)); } @Test @@ -260,11 +265,11 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // THEN the work profile notification doesn't need to be redacted - assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif)); // THEN the current user and secondary user notifications do need to be redacted - assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); - assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif)); + assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mSecondaryUserNotif)); } @Test @@ -279,11 +284,11 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // THEN the work profile notification needs to be redacted - assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mWorkProfileNotif)); // THEN the current user and secondary user notifications don't need to be redacted - assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); - assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mCurrentUserNotif)); + assertFalse(mLockscreenUserManager.notifNeedsRedactionInPublic(mSecondaryUserNotif)); } @Test @@ -298,7 +303,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // THEN the secondary profile notification still needs to be redacted because the current // user's setting takes precedence - assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + assertTrue(mLockscreenUserManager.notifNeedsRedactionInPublic(mSecondaryUserNotif)); } @Test @@ -418,9 +423,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { context, mBroadcastDispatcher, mDevicePolicyManager, + mKeyguardUpdateMonitor, + () -> mEntryManager, + () -> mOverviewProxyService, mUserManager, - (() -> mVisibilityProvider), - (() -> mNotifCollection), + () -> mVisibilityProvider, + () -> mNotifCollection, mClickNotifier, NotificationLockscreenUserManagerTest.this.mKeyguardManager, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java index 7d06abf5cd67..e43ae0d2b22a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java @@ -124,8 +124,8 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase { } private void allowPrivateNotificationsInPublic(boolean allow) { - when(mLockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn( - allow); + when(mLockScreenUserManager.sensitiveNotifsNeedRedactionInPublic(anyInt())).thenReturn( + !allow); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 0fff5f5b47e5..16b0376ba1f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -294,7 +294,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mPresenter).updateNotificationViews(any()); verify(mEntryListener).onEntryRemoved( - eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON)); + argThat(matchEntryOnKey()), any(), + eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON)); verify(mRow).setRemoved(); assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); @@ -319,8 +320,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON); - verify(mEntryListener, never()).onEntryRemoved( - eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON)); + verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()), any(), + eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON)); } /** Regression test for b/201097913. */ @@ -333,10 +334,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // Verify that only the listener for the NEW pipeline is notified. // Old pipeline: verify(mEntryListener, never()).onEntryRemoved( - argThat(matchEntryOnSbn()), any(), anyBoolean(), anyInt()); + argThat(matchEntryOnKey()), any(), anyBoolean(), anyInt()); // New pipeline: verify(mNotifCollectionListener).onEntryRemoved( - argThat(matchEntryOnSbn()), anyInt()); + argThat(matchEntryOnKey()), anyInt()); } @Test @@ -457,7 +458,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // THEN the notification is retained assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); verify(mEntryListener, never()).onEntryRemoved( - eq(mEntry), any(), eq(false), eq(UNDEFINED_DISMISS_REASON)); + argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON)); } @Test @@ -476,7 +477,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // THEN the notification is removed assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); verify(mEntryListener).onEntryRemoved( - eq(mEntry), any(), eq(false), eq(UNDEFINED_DISMISS_REASON)); + argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON)); } @Test @@ -541,7 +542,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // GIVEN interceptor that intercepts that entry when(mRemoveInterceptor.onNotificationRemoveRequested( - eq(mEntry.getKey()), eq(mEntry), anyInt())) + eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt())) .thenReturn(true); // WHEN the notification is removed @@ -549,7 +550,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // THEN the interceptor intercepts & the entry is not removed & no listeners are called assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); - verify(mEntryListener, never()).onEntryRemoved(eq(mEntry), + verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()), any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON)); } @@ -560,7 +561,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // GIVEN interceptor that doesn't intercept when(mRemoveInterceptor.onNotificationRemoveRequested( - eq(mEntry.getKey()), eq(mEntry), anyInt())) + eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt())) .thenReturn(false); // WHEN the notification is removed @@ -568,7 +569,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // THEN the interceptor intercepts & the entry is not removed & no listeners are called assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); - verify(mEntryListener, atLeastOnce()).onEntryRemoved(eq(mEntry), + verify(mEntryListener, atLeastOnce()).onEntryRemoved(argThat(matchEntryOnKey()), any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON)); } @@ -663,9 +664,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { PendingIntent.FLAG_IMMUTABLE)).build(); } - // TODO(b/201321631): Update more tests to use this function instead of eq(mEntry). - private ArgumentMatcher<NotificationEntry> matchEntryOnSbn() { - return e -> e.getSbn().equals(mSbn); + private ArgumentMatcher<NotificationEntry> matchEntryOnKey() { + return e -> e.getKey().equals(mEntry.getKey()); } private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 9ea1813377a0..4e7e79f2cb26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -1528,6 +1528,34 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void testContiguousSections() { + mListBuilder.setSectioners(List.of( + new PackageSectioner("pkg", 1), + new PackageSectioner("pkg", 1), + new PackageSectioner("pkg", 3), + new PackageSectioner("pkg", 2) + )); + } + + @Test(expected = IllegalStateException.class) + public void testNonContiguousSections() { + mListBuilder.setSectioners(List.of( + new PackageSectioner("pkg", 1), + new PackageSectioner("pkg", 1), + new PackageSectioner("pkg", 3), + new PackageSectioner("pkg", 1) + )); + } + + @Test(expected = IllegalStateException.class) + public void testBucketZeroNotAllowed() { + mListBuilder.setSectioners(List.of( + new PackageSectioner("pkg", 0), + new PackageSectioner("pkg", 1) + )); + } + + @Test public void testStabilizeGroupsDelayedSummaryRendersAllNotifsTopLevel() { // GIVEN group children posted without a summary addGroupChild(0, PACKAGE_1, GROUP_1); @@ -2189,7 +2217,11 @@ public class ShadeListBuilderTest extends SysuiTestCase { } PackageSectioner(String pkg) { - super("PackageSection_" + pkg, 0); + this(pkg, 0); + } + + PackageSectioner(String pkg, int bucket) { + super("PackageSection_" + pkg, bucket); mPackages = List.of(pkg); mComparator = null; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index f4d8405a796e..5c2f5fa6bc1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -33,7 +32,6 @@ import android.app.Notification; import android.app.NotificationManager; import android.testing.AndroidTestingRunner; -import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -41,7 +39,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.SectionClassifier; -import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -49,7 +46,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import org.junit.Before; import org.junit.Test; @@ -72,7 +68,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; @Mock private NodeController mSilentNodeController; - @Mock private SectionHeaderController mSilentHeaderController; @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; @@ -94,7 +89,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { mHighPriorityProvider, mSectionClassifier, mAlertingHeaderController, - mSilentHeaderController, mSilentNodeController); mEntry = spy(new NotificationEntryBuilder().build()); mEntry.setRanking(getRankingForUnfilteredNotif().build()); @@ -112,25 +106,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { } @Test - public void testSilentHeaderClearableChildrenUpdate() { - ListEntry listEntry = new ListEntry(mEntry.getKey(), 0L) { - @Nullable - @Override - public NotificationEntry getRepresentativeEntry() { - return mEntry; - } - }; - setRankingAmbient(false); - setSbnClearable(true); - mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); - verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); - - setSbnClearable(false); - mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); - verify(mSilentHeaderController).setClearSectionEnabled(eq(false)); - } - - @Test public void testUnfilteredState() { // GIVEN no suppressed visual effects + app not suspended mEntry.setRanking(getRankingForUnfilteredNotif().build()); @@ -225,46 +200,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { assertInSection(mEntry, mSilentSectioner); } - @Test - public void testClearableSilentSection() { - when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - setSbnClearable(true); - setRankingAmbient(false); - mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); - } - - @Test - public void testClearableMinimizedSection() { - when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - setSbnClearable(true); - setRankingAmbient(true); - mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); - } - - @Test - public void testNotClearableSilentSection() { - setSbnClearable(false); - when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - setRankingAmbient(false); - mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false)); - } - - @Test - public void testNotClearableMinimizedSection() { - setSbnClearable(false); - when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - setRankingAmbient(true); - mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry)); - verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false)); - } - private void assertInSection(NotificationEntry entry, NotifSectioner section) { for (NotifSectioner current: mSections) { if (current == section) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt deleted file mode 100644 index a2d8e3ddba24..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator - -import android.os.UserHandle -import android.service.notification.StatusBarNotification -import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.SysuiTestCase -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.NotificationLockscreenUserManager -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.DynamicPrivacyController -import com.android.systemui.statusbar.notification.collection.ListEntry -import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope -import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import dagger.BindsInstance -import dagger.Component -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever - -@SmallTest -class SensitiveContentCoordinatorTest : SysuiTestCase() { - - val dynamicPrivacyController: DynamicPrivacyController = mock() - val lockscreenUserManager: NotificationLockscreenUserManager = mock() - val pipeline: NotifPipeline = mock() - val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock() - val statusBarStateController: StatusBarStateController = mock() - val keyguardStateController: KeyguardStateController = mock() - - val coordinator: SensitiveContentCoordinator = - DaggerTestSensitiveContentCoordinatorComponent - .factory() - .create( - dynamicPrivacyController, - lockscreenUserManager, - keyguardUpdateMonitor, - statusBarStateController, - keyguardStateController) - .coordinator - - @Test - fun onDynamicPrivacyChanged_invokeInvalidationListener() { - coordinator.attach(pipeline) - val invalidator = withArgCaptor<Invalidator> { - verify(pipeline).addPreRenderInvalidator(capture()) - } - val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> { - verify(dynamicPrivacyController).addListener(capture()) - } - - val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() - invalidator.setInvalidationListener(invalidationListener) - - dynamicPrivacyListener.onDynamicPrivacyChanged() - - verify(invalidationListener).onPluggableInvalidated(invalidator) - } - - @Test - fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, false) - } - - @Test - fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, false) - } - - @Test - fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, false) - } - - @Test - fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - } - - @Test - fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(true, true) - } - - @Test - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - val entry = fakeNotification(1, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - } - - @Test - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) - - val entry = fakeNotification(2, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(true, true) - } - - @Test - fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD) - whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any())) - .thenReturn(true) - - val entry = fakeNotification(2, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!, never()).setSensitive(any(), any()) - } - - private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { - val mockUserHandle = mock<UserHandle>().apply { - whenever(identifier).thenReturn(notifUserId) - } - val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply { - whenever(user).thenReturn(mockUserHandle) - } - val mockEntry = mock<NotificationEntry>().apply { - whenever(sbn).thenReturn(mockSbn) - } - whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction) - whenever(mockEntry.rowExists()).thenReturn(true) - return object : ListEntry("key", 0) { - override fun getRepresentativeEntry(): NotificationEntry = mockEntry - } - } -} - -@CoordinatorScope -@Component(modules = [SensitiveContentCoordinatorModule::class]) -interface TestSensitiveContentCoordinatorComponent { - val coordinator: SensitiveContentCoordinator - - @Component.Factory - interface Factory { - fun create( - @BindsInstance dynamicPrivacyController: DynamicPrivacyController, - @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager, - @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor, - @BindsInstance statusBarStateController: StatusBarStateController, - @BindsInstance keyguardStateController: KeyguardStateController - ): TestSensitiveContentCoordinatorComponent - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 70266e401f8a..cf2fc7c3a5fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator +import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest @@ -43,6 +44,11 @@ import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @RunWithLooper class StackCoordinatorTest : SysuiTestCase() { + + companion object { + const val NOTIF_USER_ID = 0 + } + private lateinit var coordinator: StackCoordinator private lateinit var afterRenderListListener: OnAfterRenderListListener @@ -61,7 +67,10 @@ class StackCoordinatorTest : SysuiTestCase() { afterRenderListListener = withArgCaptor { verify(pipeline).addOnAfterRenderListListener(capture()) } - entry = NotificationEntryBuilder().setSection(section).build() + entry = NotificationEntryBuilder() + .setSection(section) + .setUser(UserHandle.of(NOTIF_USER_ID)) + .build() } @Test @@ -74,13 +83,31 @@ class StackCoordinatorTest : SysuiTestCase() { fun testSetNotificationStats_clearableAlerting() { whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController).setNotifStats(NotifStats(1, false, true, false, false)) + verify(stackController) + .setNotifStats( + NotifStats( + 1, + false, + true, + false, + false, + setOf(NOTIF_USER_ID), + emptySet())) } @Test fun testSetNotificationStats_clearableSilent() { whenever(section.bucket).thenReturn(BUCKET_SILENT) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController).setNotifStats(NotifStats(1, false, false, false, true)) + verify(stackController) + .setNotifStats( + NotifStats( + 1, + false, + false, + false, + true, + emptySet(), + setOf(NOTIF_USER_ID))) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt index b63e66f1ebe3..c7f7ec213b0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.icon; +package com.android.systemui.statusbar.notification.icon -import android.app.ActivityManager; -import android.app.Notification; +import android.app.ActivityManager +import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.Person @@ -27,11 +27,12 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.os.SystemClock import android.os.UserHandle -import android.testing.AndroidTestingRunner; +import android.testing.AndroidTestingRunner import androidx.test.InstrumentationRegistry import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any +import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -40,7 +41,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith; +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.anyInt @@ -48,15 +49,14 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) -class IconManagerTest: SysuiTestCase() { +class IconManagerTest : SysuiTestCase() { companion object { - private const val TEST_PACKAGE_NAME = "test"; - private const val TEST_UID = 0; + private const val TEST_PACKAGE_NAME = "test" + private const val TEST_UID = 0 } - private var id = 0 - private val context = InstrumentationRegistry.getTargetContext(); + private val context = InstrumentationRegistry.getTargetContext() @Mock private lateinit var shortcut: ShortcutInfo @Mock private lateinit var shortcutIc: Icon @Mock private lateinit var messageIc: Icon @@ -65,6 +65,7 @@ class IconManagerTest: SysuiTestCase() { @Mock private lateinit var drawable: Drawable @Mock private lateinit var row: ExpandableNotificationRow + @Mock private lateinit var notifLockscreenUserManager: NotificationLockscreenUserManager @Mock private lateinit var notifCollection: CommonNotifCollection @Mock private lateinit var launcherApps: LauncherApps @@ -83,13 +84,16 @@ class IconManagerTest: SysuiTestCase() { `when`(shortcut.icon).thenReturn(shortcutIc) `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc) + `when`(notifLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(TEST_UID)) + .thenReturn(true) - iconManager = IconManager(notifCollection, launcherApps, iconBuilder) + iconManager = + IconManager(notifCollection, launcherApps, iconBuilder, notifLockscreenUserManager) } @Test fun testCreateIcons_importantConversation_shortcutIcon() { - val entry = notificationEntry(true, true, true) + val entry = notificationEntry() entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) @@ -99,7 +103,7 @@ class IconManagerTest: SysuiTestCase() { @Test fun testCreateIcons_importantConversation_messageIcon() { - val entry = notificationEntry(false, true, true) + val entry = notificationEntry(hasShortcut = false) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) @@ -109,7 +113,7 @@ class IconManagerTest: SysuiTestCase() { @Test fun testCreateIcons_importantConversation_largeIcon() { - val entry = notificationEntry(false, false, true) + val entry = notificationEntry(hasShortcut = false, hasMessage = false) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) @@ -119,7 +123,7 @@ class IconManagerTest: SysuiTestCase() { @Test fun testCreateIcons_importantConversation_smallIcon() { - val entry = notificationEntry(false, false, false) + val entry = notificationEntry(hasShortcut = false, hasMessage = false, hasLargeIcon = false) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) @@ -129,7 +133,7 @@ class IconManagerTest: SysuiTestCase() { @Test fun testCreateIcons_notImportantConversation() { - val entry = notificationEntry(true, true, true) + val entry = notificationEntry() entry?.let { iconManager.createIcons(it) } @@ -138,8 +142,10 @@ class IconManagerTest: SysuiTestCase() { @Test fun testCreateIcons_sensitiveImportantConversation() { - val entry = notificationEntry(true, false, false) - entry?.setSensitive(true, true); + val entry = notificationEntry( + hasMessage = false, + hasLargeIcon = false, + hasSensitiveContent = true) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) @@ -151,14 +157,17 @@ class IconManagerTest: SysuiTestCase() { @Test fun testUpdateIcons_sensitivityChange() { - val entry = notificationEntry(true, false, false) + val entry = notificationEntry( + hasMessage = false, + hasLargeIcon = false, + hasSensitiveContent = true) entry?.channel?.isImportantConversation = true - entry?.setSensitive(true, true); entry?.let { iconManager.createIcons(it) } assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc) - entry?.setSensitive(false, false); + `when`(notifLockscreenUserManager.sensitiveNotifsNeedRedactionInPublic(TEST_UID)) + .thenReturn(false) entry?.let { iconManager.updateIcons(it) } @@ -166,14 +175,19 @@ class IconManagerTest: SysuiTestCase() { } private fun notificationEntry( - hasShortcut: Boolean, - hasMessage: Boolean, - hasLargeIcon: Boolean + hasShortcut: Boolean = true, + hasMessage: Boolean = true, + hasLargeIcon: Boolean = true, + hasSensitiveContent: Boolean = false ): NotificationEntry? { val n = Notification.Builder(mContext, "id") .setSmallIcon(smallIc) .setContentTitle("Title") .setContentText("Text") + .setVisibility( + if (hasSensitiveContent) + Notification.VISIBILITY_PRIVATE + else Notification.VISIBILITY_PUBLIC) if (hasMessage) { n.style = Notification.MessagingStyle("") @@ -203,7 +217,6 @@ class IconManagerTest: SysuiTestCase() { val entry = builder.build() entry.row = row - entry.setSensitive(false, true); return entry } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 4ea932157457..1f9af81d6508 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -91,7 +91,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testGroupSummaryNotShowingIconWhenPublic() { - mGroupRow.setSensitive(true, true); + mGroupRow.setSensitive(true); mGroupRow.setHideSensitiveForIntrinsicHeight(true); assertTrue(mGroupRow.isSummaryWithChildren()); assertFalse(mGroupRow.isShowingIcon()); @@ -99,7 +99,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testNotificationHeaderVisibleWhenAnimating() { - mGroupRow.setSensitive(true, true); + mGroupRow.setSensitive(true); mGroupRow.setHideSensitive(true, false, 0, 0); mGroupRow.setHideSensitive(false, true, 0, 0); assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper() @@ -130,7 +130,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { public void testIconColorShouldBeUpdatedWhenSensitive() throws Exception { ExpandableNotificationRow row = spy(mNotificationTestHelper.createRow( FLAG_CONTENT_VIEW_ALL)); - row.setSensitive(true, true); + row.setSensitive(true); row.setHideSensitive(true, false, 0, 0); verify(row).updateShelfIconColor(); } @@ -214,7 +214,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testFeedback_noHeader() { // public notification is custom layout - no header - mGroupRow.setSensitive(true, true); + mGroupRow.setSensitive(true); mGroupRow.setOnFeedbackClickListener(null); mGroupRow.setFeedbackIcon(null); } @@ -318,21 +318,23 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testGetIsNonblockable_oemLocked() throws Exception { + public void testGetIsNonblockable_criticalDeviceFunction() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); - row.getEntry().getChannel().setImportanceLockedByOEM(true); + row.getEntry().getChannel().setImportanceLockedByCriticalDeviceFunction(true); + row.getEntry().getChannel().setBlockable(false); assertTrue(row.getIsNonblockable()); } @Test - public void testGetIsNonblockable_criticalDeviceFunction() throws Exception { + public void testGetIsNonblockable_criticalDeviceFunction_butBlockable() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); row.getEntry().getChannel().setImportanceLockedByCriticalDeviceFunction(true); + row.getEntry().getChannel().setBlockable(true); - assertTrue(row.getIsNonblockable()); + assertFalse(row.getIsNonblockable()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index 251ac7d250fe..a8557050dddb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -78,7 +78,6 @@ import com.android.systemui.statusbar.notification.collection.legacy.Notificatio import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.icon.IconBuilder; import com.android.systemui.statusbar.notification.icon.IconManager; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; @@ -132,7 +131,6 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { @Mock private NotificationEntryListener mEntryListener; @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback; @Mock private HeadsUpManager mHeadsUpManager; - @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationGutsManager mGutsManager; @Mock private NotificationRemoteInputManager mRemoteInputManager; @@ -254,6 +252,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { .thenAnswer((Answer<ExpandableNotificationRowController>) invocation -> new ExpandableNotificationRowController( viewCaptor.getValue(), + mLockscreenUserManager, mock(ActivatableNotificationViewController.class), mock(RemoteInputViewSubcomponent.Factory.class), mock(MetricsLogger.class), @@ -300,7 +299,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { new IconManager( mEntryManager, mock(LauncherApps.class), - new IconBuilder(mContext)), + new IconBuilder(mContext), + mLockscreenUserManager), mock(LowPriorityInflationHelper.class), mNotifPipelineFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 1ecb09bc8514..d5ed37a570d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -52,6 +52,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -152,7 +153,8 @@ public class NotificationTestHelper { mIconManager = new IconManager( mock(CommonNotifCollection.class), mock(LauncherApps.class), - new IconBuilder(mContext)); + new IconBuilder(mContext), + mock(NotificationLockscreenUserManager.class)); NotificationContentInflater contentBinder = new NotificationContentInflater( mock(NotifRemoteViewCache.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 4270d72770dc..3f190367f284 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -5,8 +5,9 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationShelf -import junit.framework.Assert.assertFalse -import junit.framework.Assert.assertTrue +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.row.ExpandableView +import junit.framework.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -143,6 +144,113 @@ class NotificationShelfTest : SysuiTestCase() { assertFalse(isYBelowShelfInView) } + @Test + fun getAmountInShelf_lastViewBelowShelf_completelyInShelf() { + val shelfClipStart = 0f + val viewStart = 1f + + val expandableView = mock(ExpandableView::class.java) + whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(expandableView.translationY).thenReturn(viewStart) + whenever(expandableView.actualHeight).thenReturn(20) + + whenever(expandableView.minHeight).thenReturn(20) + whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(expandableView.isInShelf).thenReturn(true) + + whenever(ambientState.isOnKeyguard).thenReturn(true) + whenever(ambientState.isExpansionChanging).thenReturn(false) + whenever(ambientState.isShadeExpanded).thenReturn(true) + + val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, + /* view= */ expandableView, + /* scrollingFast= */ false, + /* expandingAnimated= */ false, + /* isLastChild= */ true, + shelfClipStart) + assertEquals(1f, amountInShelf) + } + + @Test + fun getAmountInShelf_lastViewAlmostBelowShelf_completelyInShelf() { + val viewStart = 0f + val shelfClipStart = 0.001f + + val expandableView = mock(ExpandableView::class.java) + whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(expandableView.translationY).thenReturn(viewStart) + whenever(expandableView.actualHeight).thenReturn(20) + + whenever(expandableView.minHeight).thenReturn(20) + whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(expandableView.isInShelf).thenReturn(true) + + whenever(ambientState.isOnKeyguard).thenReturn(true) + whenever(ambientState.isExpansionChanging).thenReturn(false) + whenever(ambientState.isShadeExpanded).thenReturn(true) + + val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, + /* view= */ expandableView, + /* scrollingFast= */ false, + /* expandingAnimated= */ false, + /* isLastChild= */ true, + shelfClipStart) + assertEquals(1f, amountInShelf) + } + + @Test + fun getAmountInShelf_lastViewHalfClippedByShelf_halfInShelf() { + val viewStart = 0f + val shelfClipStart = 10f + + val expandableView = mock(ExpandableView::class.java) + whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(expandableView.translationY).thenReturn(viewStart) + whenever(expandableView.actualHeight).thenReturn(25) + + whenever(expandableView.minHeight).thenReturn(25) + whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(expandableView.isInShelf).thenReturn(true) + + whenever(ambientState.isOnKeyguard).thenReturn(true) + whenever(ambientState.isExpansionChanging).thenReturn(false) + whenever(ambientState.isShadeExpanded).thenReturn(true) + + val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, + /* view= */ expandableView, + /* scrollingFast= */ false, + /* expandingAnimated= */ false, + /* isLastChild= */ true, + shelfClipStart) + assertEquals(0.5f, amountInShelf) + } + + @Test + fun getAmountInShelf_lastViewAboveShelf_notInShelf() { + val viewStart = 0f + val shelfClipStart = 15f + + val expandableView = mock(ExpandableView::class.java) + whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) + whenever(expandableView.translationY).thenReturn(viewStart) + whenever(expandableView.actualHeight).thenReturn(10) + + whenever(expandableView.minHeight).thenReturn(10) + whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY + whenever(expandableView.isInShelf).thenReturn(false) + + whenever(ambientState.isExpansionChanging).thenReturn(false) + whenever(ambientState.isOnKeyguard).thenReturn(true) + + val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, + /* view= */ expandableView, + /* scrollingFast= */ false, + /* expandingAnimated= */ false, + /* isLastChild= */ true, + shelfClipStart) + assertEquals(0f, amountInShelf) + } + private fun setFractionToShade(fraction: Float) { whenever(ambientState.fractionToShade).thenReturn(fraction) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index e8608fa76c06..63e0f53e093d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -31,6 +31,8 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -612,6 +614,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertTrue(mStackScroller.isInsideQsHeader(event2)); } + @Test + public void setFractionToShade_recomputesStackHeight() { + mStackScroller.setFractionToShade(1f); + verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat()); + } + private void setBarStateForTest(int state) { // Can't inject this through the listener or we end up on the actual implementation // rather than the mock because the spy just coppied the anonymous inner /shruggie. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index ed22cd3c55fc..169c04c692e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -36,6 +36,7 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -57,9 +58,12 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { private final NotificationStackScrollLayoutController mStackScrollerController = mock(NotificationStackScrollLayoutController.class); - private final NotificationPanelViewController mPanelView = + private final NotificationPanelViewController mPanelViewController = mock(NotificationPanelViewController.class); private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class); + private final NotificationLockscreenUserManager mLockscreenUserManager = + mock(NotificationLockscreenUserManager.class); + private HeadsUpAppearanceController mHeadsUpAppearanceController; private ExpandableNotificationRow mFirst; private HeadsUpStatusBarView mHeadsUpStatusBarView; @@ -93,12 +97,13 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mHeadsUpManager, mStatusbarStateController, mBypassController, + mLockscreenUserManager, mWakeUpCoordinator, mDarkIconDispatcher, mKeyguardStateController, mCommandQueue, mStackScrollerController, - mPanelView, + mPanelViewController, mHeadsUpStatusBarView, new Clock(mContext, null), Optional.of(mOperatorNameView)); @@ -175,12 +180,13 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mHeadsUpManager, mStatusbarStateController, mBypassController, + mLockscreenUserManager, mWakeUpCoordinator, mDarkIconDispatcher, mKeyguardStateController, mCommandQueue, mStackScrollerController, - mPanelView, + mPanelViewController, mHeadsUpStatusBarView, new Clock(mContext, null), Optional.empty()); @@ -193,15 +199,15 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { public void testDestroy() { reset(mHeadsUpManager); reset(mDarkIconDispatcher); - reset(mPanelView); + reset(mPanelViewController); reset(mStackScrollerController); mHeadsUpAppearanceController.onViewDetached(); verify(mHeadsUpManager).removeListener(any()); verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any()); - verify(mPanelView).removeTrackingHeadsUpListener(any()); - verify(mPanelView).setHeadsUpAppearanceController(isNull()); + verify(mPanelViewController).removeTrackingHeadsUpListener(any()); + verify(mPanelViewController).setHeadsUpAppearanceController(isNull()); verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index f43c2a183465..9c02216722e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -475,4 +475,19 @@ public class KeyguardBouncerTest extends SysuiTestCase { mBouncer.setExpansion(bouncerHideAmount); verify(callback, never()).onExpansionChanged(bouncerHideAmount); } + + @Test + public void testOnResumeCalledForFullscreenBouncerOnSecondShow() { + // GIVEN a security mode which requires fullscreen bouncer + when(mKeyguardSecurityModel.getSecurityMode(anyInt())) + .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin); + mBouncer.show(true); + + // WHEN a second call to show occurs, the bouncer will already by visible + reset(mKeyguardHostViewController); + mBouncer.show(true); + + // THEN ensure the ViewController is told to resume + verify(mKeyguardHostViewController).onResume(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 11f96cec6369..356d002e9da9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -1009,6 +1009,17 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void testExpandWithQsMethodIsUsingLockscreenTransitionController() { + enableSplitShade(/* enabled= */ true); + mStatusBarStateController.setState(KEYGUARD); + + mNotificationPanelViewController.expandWithQs(); + + verify(mLockscreenShadeTransitionController).goToLockedShade( + /* expandedView= */null, /* needsQSAnimation= */false); + } + + @Test public void testUnlockAnimationDoesNotAffectScrim() { mNotificationPanelViewController.onUnlockHintStarted(); verify(mScrimController).setExpansionAffectsAlpha(false); diff --git a/services/Android.bp b/services/Android.bp index 1e4ce19f1541..70692a63ff0f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -102,7 +102,6 @@ filegroup { ":services.profcollect-sources", ":services.restrictions-sources", ":services.searchui-sources", - ":services.selectiontoolbar-sources", ":services.smartspace-sources", ":services.speech-sources", ":services.systemcaptions-sources", @@ -158,7 +157,6 @@ java_library { "services.profcollect", "services.restrictions", "services.searchui", - "services.selectiontoolbar", "services.smartspace", "services.speech", "services.systemcaptions", diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java index bc5cb0250d56..99526b7ef0d1 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java @@ -26,7 +26,7 @@ import com.android.internal.backup.ITransportStatusCallback; public class TransportStatusCallback extends ITransportStatusCallback.Stub { private static final String TAG = "TransportStatusCallback"; - private static final int TIMEOUT_MILLIS = 600 * 1000; // 10 minutes. + private static final int TIMEOUT_MILLIS = 300 * 1000; // 5 minutes. private static final int OPERATION_STATUS_DEFAULT = 0; private final int mOperationTimeout; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 3b11038daf7e..9b2554fdbb3c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -62,7 +62,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; @@ -81,7 +80,6 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; -import android.text.BidiFormatter; import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Log; @@ -294,14 +292,13 @@ public class CompanionDeviceManagerService extends SystemService { private boolean onCompanionApplicationBindingDiedInternal( @UserIdInt int userId, @NonNull String packageName) { - // Update the current connected devices sets when binderDied, so that application is able - // to call notifyDeviceAppeared after re-launch the application. for (AssociationInfo ai : mAssociationStore.getAssociationsForPackage(userId, packageName)) { - int id = ai.getId(); - Slog.i(TAG, "Removing association id: " + id + " for package: " - + packageName + " due to binderDied."); - mDevicePresenceMonitor.removeDeviceFromMonitoring(id); + final int associationId = ai.getId(); + if (ai.isSelfManaged() + && mDevicePresenceMonitor.isDevicePresent(associationId)) { + mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId); + } } // TODO(b/218613015): implement. return false; @@ -538,20 +535,12 @@ public class CompanionDeviceManagerService extends SystemService { String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); // TODO: check userId. - String packageTitle = BidiFormatter.getInstance().unicodeWrap( - getPackageInfo(getContext(), userId, callingPackage) - .applicationInfo - .loadSafeLabel(getContext().getPackageManager(), - PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, - PackageItemInfo.SAFE_LABEL_FLAG_TRIM - | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE) - .toString()); final long identity = Binder.clearCallingIdentity(); try { return PendingIntent.getActivityAsUser(getContext(), 0 /* request code */, NotificationAccessConfirmationActivityContract.launcherIntent( - getContext(), userId, component, packageTitle), + getContext(), userId, component), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null /* options */, diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index 37e83697c787..89ed301eb661 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -149,6 +149,13 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported"); } + /** + * Marks a "self-managed" device as disconnected when binderDied. + */ + public void onSelfManagedDeviceReporterBinderDied(int associationId) { + onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported"); + } + @Override public void onBluetoothCompanionDeviceConnected(int associationId) { onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt"); @@ -259,16 +266,6 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange } /** - * Remove the current connected devices by associationId. - */ - public void removeDeviceFromMonitoring(int associationId) { - mConnectedBtDevices.remove(associationId); - mNearbyBleDevices.remove(associationId); - mReportedSelfManagedDevices.remove(associationId); - mSimulated.remove(associationId); - } - - /** * Implements * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} */ @@ -280,7 +277,9 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange Log.d(TAG, " > association=" + association); } - removeDeviceFromMonitoring(id); + mConnectedBtDevices.remove(id); + mNearbyBleDevices.remove(id); + mReportedSelfManagedDevices.remove(id); // Do NOT call mCallback.onDeviceDisappeared()! // CompanionDeviceManagerService will know that the association is removed, and will do diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 06f698efde2b..ed6110086089 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -334,10 +334,14 @@ public abstract class PackageManagerInternal { /** * Retrieve all receivers that can handle a broadcast of the given intent. + * @param filterCallingUid The results will be filtered in the context of this UID instead + * of the calling UID. + * @param forSend true if the invocation is intended for sending broadcasts. The value + * of this parameter affects how packages are filtered. */ public abstract List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, - int filterCallingUid, int userId); + int filterCallingUid, int userId, boolean forSend); /** * Retrieve all services that can be performed for the given intent. @@ -371,10 +375,10 @@ public abstract class PackageManagerInternal { int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners); /** - * Called by Owners to set the package names protected by the device owner. + * Marks packages as protected for a given user or all users in case of USER_ALL. */ - public abstract void setDeviceOwnerProtectedPackages( - String deviceOwnerPackageName, List<String> packageNames); + public abstract void setOwnerProtectedPackages( + @UserIdInt int userId, @NonNull List<String> packageNames); /** * Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}. diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index bc40170d39b7..5eec6e58e925 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3880,9 +3880,12 @@ class StorageManagerService extends IStorageManager.Stub match = vol.isVisibleForWrite(userId) || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia)); } else { + // Return both read only and write only volumes. When includeSharedProfile is + // true, all the volumes of userIdSharingMedia should be returned when queried + // from the user it shares media with match = vol.isVisibleForUser(userId) || (!vol.isVisible() && includeInvisible && vol.getPath() != null) - || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia)); + || (includeSharedProfile && vol.isVisibleForUser(userIdSharingMedia)); } if (!match) continue; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 1033aea4b09f..7a52af61a196 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -3066,42 +3066,88 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { Binder.restoreCallingIdentity(ident); } + // Send the broadcast exactly once to all possible disjoint sets of apps. + // If the location master switch is on, broadcast the ServiceState 4 times: + // - Full ServiceState sent to apps with ACCESS_FINE_LOCATION and READ_PHONE_STATE + // - Full ServiceState sent to apps with ACCESS_FINE_LOCATION and + // READ_PRIVILEGED_PHONE_STATE but not READ_PHONE_STATE + // - Sanitized ServiceState sent to apps with READ_PHONE_STATE but not ACCESS_FINE_LOCATION + // - Sanitized ServiceState sent to apps with READ_PRIVILEGED_PHONE_STATE but neither + // READ_PHONE_STATE nor ACCESS_FINE_LOCATION + // If the location master switch is off, broadcast the ServiceState multiple times: + // - Full ServiceState sent to all apps permitted to bypass the location master switch if + // they have either READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE + // - Sanitized ServiceState sent to all other apps with READ_PHONE_STATE + // - Sanitized ServiceState sent to all other apps with READ_PRIVILEGED_PHONE_STATE but not + // READ_PHONE_STATE + if (Binder.withCleanCallingIdentity(() -> + LocationAccessPolicy.isLocationModeEnabled(mContext, mContext.getUserId()))) { + Intent fullIntent = createServiceStateIntent(state, subId, phoneId, false); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + fullIntent, + new String[]{Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION}); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + fullIntent, + new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION}, + new String[]{Manifest.permission.READ_PHONE_STATE}); + + Intent sanitizedIntent = createServiceStateIntent(state, subId, phoneId, true); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + sanitizedIntent, + new String[]{Manifest.permission.READ_PHONE_STATE}, + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + sanitizedIntent, + new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE}, + new String[]{Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION}); + } else { + String[] locationBypassPackages = Binder.withCleanCallingIdentity(() -> + LocationAccessPolicy.getLocationBypassPackages(mContext)); + for (String locationBypassPackage : locationBypassPackages) { + Intent fullIntent = createServiceStateIntent(state, subId, phoneId, false); + fullIntent.setPackage(locationBypassPackage); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + fullIntent, + new String[]{Manifest.permission.READ_PHONE_STATE}); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + fullIntent, + new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE}, + new String[]{Manifest.permission.READ_PHONE_STATE}); + } + + Intent sanitizedIntent = createServiceStateIntent(state, subId, phoneId, true); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + sanitizedIntent, + new String[]{Manifest.permission.READ_PHONE_STATE}, + new String[]{/* no excluded permissions */}, + locationBypassPackages); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions( + sanitizedIntent, + new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE}, + new String[]{Manifest.permission.READ_PHONE_STATE}, + locationBypassPackages); + } + } + + private Intent createServiceStateIntent(ServiceState state, int subId, int phoneId, + boolean sanitizeLocation) { Intent intent = new Intent(Intent.ACTION_SERVICE_STATE); intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); Bundle data = new Bundle(); - state.fillInNotifierBundle(data); + if (sanitizeLocation) { + state.createLocationInfoSanitizedCopy(true).fillInNotifierBundle(data); + } else { + state.fillInNotifierBundle(data); + } intent.putExtras(data); - // Pass the subscription along with the intent. intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId); intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId); intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId); - - // Send the broadcast twice -- once for all apps with READ_PHONE_STATE, then again - // for all apps with READ_PRIVILEGED_PHONE_STATE but not READ_PHONE_STATE. - // Do this again twice, the first time for apps with ACCESS_FINE_LOCATION, then again with - // the location-sanitized service state for all apps without ACCESS_FINE_LOCATION. - // This ensures that any app holding either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE - // get this broadcast exactly once, and we are not exposing location without permission. - mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, - new String[] {Manifest.permission.READ_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION}); - mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, - new String[] {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION}, - new String[] {Manifest.permission.READ_PHONE_STATE}); - - // Replace bundle with location-sanitized ServiceState - data = new Bundle(); - state.createLocationInfoSanitizedCopy(true).fillInNotifierBundle(data); - intent.putExtras(data); - mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, - new String[] {Manifest.permission.READ_PHONE_STATE}, - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}); - mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, - new String[] {Manifest.permission.READ_PRIVILEGED_PHONE_STATE}, - new String[] {Manifest.permission.READ_PHONE_STATE, - Manifest.permission.ACCESS_FINE_LOCATION}); + return intent; } private void broadcastSignalStrengthChanged(SignalStrength signalStrength, int phoneId, diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 6fa3bc8e0669..a792e9e805e8 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -6593,12 +6593,11 @@ public final class ActiveServices { } final int uidState = mAm.getUidStateLocked(callingUid); - int callerTargetSdkVersion = INVALID_UID; + int callerTargetSdkVersion = -1; try { - ApplicationInfo ai = mAm.mContext.getPackageManager().getApplicationInfoAsUser( - callingPackage, PackageManager.MATCH_KNOWN_PACKAGES, userId); - callerTargetSdkVersion = ai.targetSdkVersion; - } catch (PackageManager.NameNotFoundException e) { + callerTargetSdkVersion = mAm.mContext.getPackageManager() + .getTargetSdkVersion(callingPackage); + } catch (PackageManager.NameNotFoundException ignored) { } final String debugInfo = "[callingPackage: " + callingPackage diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index dea8cc015272..f6e8bc826153 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2627,7 +2627,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void batterySendBroadcast(Intent intent) { synchronized (this) { broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null, - OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(), + null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } } @@ -4241,7 +4241,7 @@ public class ActivityManagerService extends IActivityManager.Stub intent.putExtra(Intent.EXTRA_UID, uid); intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid)); broadcastIntentLocked(null, null, null, intent, - null, null, 0, null, null, null, null, OP_NONE, + null, null, 0, null, null, null, null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.getUserId(uid)); } @@ -8122,7 +8122,7 @@ public class ActivityManagerService extends IActivityManager.Stub | Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId); broadcastIntentLocked(null, null, null, intent, - null, null, 0, null, null, null, null, OP_NONE, + null, null, 0, null, null, null, null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid, currentUserId); intent = new Intent(Intent.ACTION_USER_STARTING); @@ -8134,8 +8134,8 @@ public class ActivityManagerService extends IActivityManager.Stub public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {} - }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, OP_NONE, - null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid, + }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, null, + OP_NONE, null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid, UserHandle.USER_ALL); } catch (Throwable e) { Slog.wtf(TAG, "Failed sending first user broadcasts", e); @@ -13165,8 +13165,8 @@ public class ActivityManagerService extends IActivityManager.Stub Intent intent = allSticky.get(i); BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, - null, null, -1, -1, false, null, null, null, OP_NONE, null, receivers, - null, 0, null, null, false, true, true, -1, false, null, + null, null, -1, -1, false, null, null, null, null, OP_NONE, null, + receivers, null, 0, null, null, false, true, true, -1, false, null, false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */); queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); @@ -13248,8 +13248,8 @@ public class ActivityManagerService extends IActivityManager.Stub UserManager.DISALLOW_DEBUGGING_FEATURES, user)) { continue; } - List<ResolveInfo> newReceivers = mPackageManagerInt - .queryIntentReceivers(intent, resolvedType, pmFlags, callingUid, user); + List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers( + intent, resolvedType, pmFlags, callingUid, user, true /* forSend */); if (user != UserHandle.USER_SYSTEM && newReceivers != null) { // If this is not the system user, we need to check for // any receivers that should be filtered out. @@ -13265,8 +13265,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (newReceivers != null) { for (int i = newReceivers.size() - 1; i >= 0; i--) { final ResolveInfo ri = newReceivers.get(i); - final Resolution<ResolveInfo> resolution = mComponentAliasResolver - .resolveReceiver(intent, ri, resolvedType, pmFlags, user, callingUid); + final Resolution<ResolveInfo> resolution = + mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType, + pmFlags, user, callingUid, true /* forSend */); if (resolution == null) { // It was an alias, but the target was not found. newReceivers.remove(i); @@ -13421,12 +13422,14 @@ public class ActivityManagerService extends IActivityManager.Stub String callerPackage, String callerFeatureId, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, String[] requiredPermissions, String[] excludedPermissions, - int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid, + String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered, + boolean sticky, int callingPid, int callingUid, int realCallingUid, int realCallingPid, int userId) { return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions, - excludedPermissions, appOp, bOptions, ordered, sticky, callingPid, callingUid, - realCallingUid, realCallingPid, userId, false /* allowBackgroundActivityStarts */, + excludedPermissions, excludedPackages, appOp, bOptions, ordered, sticky, callingPid, + callingUid, realCallingUid, realCallingPid, userId, + false /* allowBackgroundActivityStarts */, null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */); } @@ -13435,7 +13438,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Nullable String callerFeatureId, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, String[] requiredPermissions, - String[] excludedPermissions, int appOp, Bundle bOptions, + String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid, int realCallingPid, int userId, boolean allowBackgroundActivityStarts, @@ -14042,10 +14045,10 @@ public class ActivityManagerService extends IActivityManager.Stub final BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, - requiredPermissions, excludedPermissions, appOp, brOptions, registeredReceivers, - resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, - allowBackgroundActivityStarts, backgroundActivityStartsToken, - timeoutExempt); + requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions, + registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered, + sticky, false, userId, allowBackgroundActivityStarts, + backgroundActivityStartsToken, timeoutExempt); if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r); final boolean replaced = replacePending && (queue.replaceParallelBroadcastLocked(r) != null); @@ -14140,7 +14143,7 @@ public class ActivityManagerService extends IActivityManager.Stub BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, - requiredPermissions, excludedPermissions, appOp, brOptions, + requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts, backgroundActivityStartsToken, timeoutExempt); @@ -14269,14 +14272,16 @@ public class ActivityManagerService extends IActivityManager.Stub String[] requiredPermissions, int appOp, Bundle bOptions, boolean serialized, boolean sticky, int userId) { return broadcastIntentWithFeature(caller, null, intent, resolvedType, resultTo, resultCode, - resultData, resultExtras, requiredPermissions, null, appOp, bOptions, serialized, - sticky, userId); + resultData, resultExtras, requiredPermissions, null, null, appOp, bOptions, + serialized, sticky, userId); } + @Override public final int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, - String[] requiredPermissions, String[] excludedPermissions, int appOp, Bundle bOptions, + String[] requiredPermissions, String[] excludedPermissions, + String[] excludedPackages, int appOp, Bundle bOptions, boolean serialized, boolean sticky, int userId) { enforceNotIsolatedCaller("broadcastIntent"); synchronized(this) { @@ -14291,8 +14296,8 @@ public class ActivityManagerService extends IActivityManager.Stub return broadcastIntentLocked(callerApp, callerApp != null ? callerApp.info.packageName : null, callingFeatureId, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, - requiredPermissions, excludedPermissions, appOp, bOptions, serialized, - sticky, callingPid, callingUid, callingUid, callingPid, userId); + requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions, + serialized, sticky, callingPid, callingUid, callingUid, callingPid, userId); } finally { Binder.restoreCallingIdentity(origId); } @@ -14315,7 +14320,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions, null, - OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid, + null, OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid, realCallingPid, userId, allowBackgroundActivityStarts, backgroundActivityStartsToken, broadcastAllowList); } finally { @@ -16834,10 +16839,11 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/, null /*callerPackage*/, null /*callingFeatureId*/, intent, null /*resolvedType*/, resultTo, 0 /*resultCode*/, null /*resultData*/, - null /*resultExtras*/, requiredPermissions, null, AppOpsManager.OP_NONE, - bOptions /*options*/, serialized, false /*sticky*/, callingPid, - callingUid, callingUid, callingPid, userId, - false /*allowBackgroundStarts*/, + null /*resultExtras*/, requiredPermissions, + null /*excludedPermissions*/, null /*excludedPackages*/, + AppOpsManager.OP_NONE, bOptions /*options*/, serialized, + false /*sticky*/, callingPid, callingUid, callingUid, callingPid, + userId, false /*allowBackgroundStarts*/, null /*tokenNeededForBackgroundActivityStarts*/, appIdAllowList); } finally { Binder.restoreCallingIdentity(origId); @@ -16973,7 +16979,7 @@ public class ActivityManagerService extends IActivityManager.Stub | Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, - null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, + null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) { intent = new Intent(Intent.ACTION_LOCALE_CHANGED); @@ -16988,8 +16994,8 @@ public class ActivityManagerService extends IActivityManager.Stub TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, PowerExemptionManager.REASON_LOCALE_CHANGED, ""); broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, - null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, SYSTEM_UID, - Binder.getCallingUid(), Binder.getCallingPid(), + null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, + SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } @@ -17004,8 +17010,9 @@ public class ActivityManagerService extends IActivityManager.Stub String[] permissions = new String[] { android.Manifest.permission.INSTALL_PACKAGES }; broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, - permissions, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID, - Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); + permissions, null, null, OP_NONE, null, false, false, MY_PID, + SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), + UserHandle.USER_ALL); } } } @@ -17029,8 +17036,8 @@ public class ActivityManagerService extends IActivityManager.Stub } broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, - null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(), - Binder.getCallingPid(), UserHandle.USER_ALL); + null, null, OP_NONE, null, false, false, -1, SYSTEM_UID, + Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); } } @@ -17195,17 +17202,17 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean isUidCurrentlyInstrumented(int uid) { + public int getInstrumentationSourceUid(int uid) { synchronized (mProcLock) { for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) { ActiveInstrumentation activeInst = mActiveInstrumentation.get(i); if (!activeInst.mFinished && activeInst.mTargetInfo != null && activeInst.mTargetInfo.uid == uid) { - return true; + return activeInst.mSourceUid; } } } - return false; + return INVALID_UID; } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 402491d8fe80..397a4420700e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -804,8 +804,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.flush(); Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle(); mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null, - requiredPermissions, null, android.app.AppOpsManager.OP_NONE, bundle, true, false, - mUserId); + requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, bundle, true, + false, mUserId); if (!mAsync) { receiver.waitForFinish(); } diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 5a234f5010dd..c09bb2d4dc28 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -67,6 +67,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -360,6 +361,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mUidBatteryUsageInWindow.removeAt(i); } } + mInjector.getPolicy().onUserRemovedLocked(userId); } } @@ -368,6 +370,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> synchronized (mLock) { mUidBatteryUsage.delete(uid); mUidBatteryUsageInWindow.delete(uid); + mInjector.getPolicy().onUidRemovedLocked(uid); } } @@ -1208,6 +1211,14 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window"; /** + * The grace period after an interaction event with the app, if the background current + * drain goes beyond the threshold within that period, the system won't apply the + * restrictions. + */ + static final String KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_interaction_grace_period"; + + /** * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher * value for the legitimate cases with higher background current drain. */ @@ -1310,6 +1321,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final long mDefaultBgCurrentDrainWindowMs; /** + * Default value to {@link #mBgCurrentDrainInteractionGracePeriodMs}. + */ + final long mDefaultBgCurrentDrainInteractionGracePeriodMs; + + /** * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. */ @@ -1394,6 +1410,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> volatile long mBgCurrentDrainWindowMs; /** + * @see #KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD. + */ + volatile long mBgCurrentDrainInteractionGracePeriodMs; + + /** * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION. */ volatile long mBgCurrentDrainMediaPlaybackMinDuration; @@ -1455,6 +1476,12 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> private final SparseArray<Pair<long[], ImmutableBatteryUsage[]>> mHighBgBatteryPackages = new SparseArray<>(); + /** + * The timestamp of the last interaction, key is the UID. + */ + @GuardedBy("mLock") + private final SparseLongArray mLastInteractionTime = new SparseLongArray(); + @NonNull private final Object mLock; @@ -1478,6 +1505,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> isLowRamDeviceStatic() ? val[1] : val[0]; mDefaultBgCurrentDrainWindowMs = resources.getInteger( R.integer.config_bg_current_drain_window) * 1_000; + mDefaultBgCurrentDrainInteractionGracePeriodMs = mDefaultBgCurrentDrainWindowMs; val = getFloatArray(resources.obtainTypedArray( R.array.config_bg_current_drain_high_threshold_to_restricted_bucket)); mDefaultBgCurrentDrainRestrictedBucketHighThreshold = @@ -1511,6 +1539,8 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mBgCurrentDrainBgRestrictedThreshold[1] = mDefaultBgCurrentDrainBgRestrictedHighThreshold; mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs; + mBgCurrentDrainInteractionGracePeriodMs = + mDefaultBgCurrentDrainInteractionGracePeriodMs; mBgCurrentDrainMediaPlaybackMinDuration = mDefaultBgCurrentDrainMediaPlaybackMinDuration; mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration; @@ -1542,6 +1572,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> case KEY_BG_CURRENT_DRAIN_WINDOW: updateCurrentDrainWindow(); break; + case KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD: + updateCurrentDrainInteractionGracePeriod(); + break; case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION: updateCurrentDrainMediaPlaybackMinDuration(); break; @@ -1626,6 +1659,13 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mDefaultBgCurrentDrainWindowMs); } + private void updateCurrentDrainInteractionGracePeriod() { + mBgCurrentDrainInteractionGracePeriodMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD, + mDefaultBgCurrentDrainInteractionGracePeriodMs); + } + private void updateCurrentDrainMediaPlaybackMinDuration() { mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -1668,6 +1708,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> super.onSystemReady(); updateCurrentDrainThreshold(); updateCurrentDrainWindow(); + updateCurrentDrainInteractionGracePeriod(); updateCurrentDrainMediaPlaybackMinDuration(); updateCurrentDrainLocationMinDuration(); updateCurrentDrainEventDurationBasedThresholdEnabled(); @@ -1685,8 +1726,10 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> synchronized (mLock) { final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid); if (pair != null) { + final long lastInteractionTime = mLastInteractionTime.get(uid, 0L); final long[] ts = pair.first; - final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0 + final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] + > (lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) && mTracker.mAppRestrictionController.isAutoRestrictAbusiveAppEnabled() ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; @@ -1777,6 +1820,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> // We're already in the background restricted level, nothing more we could do. return; } + final long lastInteractionTime = mLastInteractionTime.get(uid, 0L); final long now = SystemClock.elapsedRealtime(); final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now, mBgCurrentDrainWindowMs); @@ -1788,13 +1832,17 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> long[] ts = null; ImmutableBatteryUsage[] usages = null; if (rbPercentage >= rbThreshold) { - // New findings to us, track it and let the controller know. - ts = new long[TIME_STAMP_INDEX_LAST]; - ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; - usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST]; - usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; - mHighBgBatteryPackages.put(uid, Pair.create(ts, usages)); - notifyController = excessive = true; + if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) { + // New findings to us, track it and let the controller know. + ts = new long[TIME_STAMP_INDEX_LAST]; + ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; + usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST]; + usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + mHighBgBatteryPackages.put(uid, Pair.create(ts, usages)); + // It's beeen long enough since last interaction with this app. + notifyController = true; + } + excessive = true; } if (decoupleThresholds && brPercentage >= brThreshold) { if (ts == null) { @@ -1812,11 +1860,15 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final long[] ts = pair.first; final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; if (rbPercentage >= rbThreshold) { - if (lastRestrictBucketTs == 0) { - ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; - pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) { + if (lastRestrictBucketTs == 0) { + ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; + pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + } + // It's been long enough since last interaction with this app. + notifyController = true; } - notifyController = excessive = true; + excessive = true; } else { // It's actually back to normal, but we don't untrack it until // explicit user interactions, because the restriction could be the cause @@ -1833,7 +1885,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> && (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs)); if (notifyController) { ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; - pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = usage; } excessive = true; } else { @@ -1841,7 +1893,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> // user consent to unrestrict it; or if it's in restricted bucket level, // resetting this won't lift it from that level. ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0; - pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = null; + pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null; // Now need to notify the controller. } } @@ -1902,6 +1954,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> void onUserInteractionStarted(String packageName, int uid) { boolean changed = false; synchronized (mLock) { + mLastInteractionTime.put(uid, SystemClock.elapsedRealtime()); final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel( uid, packageName); if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { @@ -1940,9 +1993,30 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> @VisibleForTesting void reset() { mHighBgBatteryPackages.clear(); + mLastInteractionTime.clear(); mTracker.reset(); } + @GuardedBy("mLock") + void onUserRemovedLocked(final @UserIdInt int userId) { + for (int i = mHighBgBatteryPackages.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mHighBgBatteryPackages.keyAt(i)) == userId) { + mHighBgBatteryPackages.removeAt(i); + } + } + for (int i = mLastInteractionTime.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mLastInteractionTime.keyAt(i)) == userId) { + mLastInteractionTime.removeAt(i); + } + } + } + + @GuardedBy("mLock") + void onUidRemovedLocked(final int uid) { + mHighBgBatteryPackages.remove(uid); + mLastInteractionTime.delete(uid); + } + @Override void dump(PrintWriter pw, String prefix) { pw.print(prefix); @@ -1976,6 +2050,10 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> pw.print('='); pw.println(mBgCurrentDrainWindowMs); pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD); + pw.print('='); + pw.println(mBgCurrentDrainInteractionGracePeriodMs); + pw.print(prefix); pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION); pw.print('='); pw.println(mBgCurrentDrainMediaPlaybackMinDuration); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index dd7fb84b46bf..d2e40c56c772 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -860,6 +860,21 @@ public final class BroadcastQueue { } } + // Check that the receiver does *not* belong to any of the excluded packages + if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) { + if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) { + Slog.w(TAG, "Skipping delivery of excluded package " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " excludes package " + filter.packageName + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + } + // If the broadcast also requires an app op check that as well. if (!skip && r.appOp != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(r.appOp, @@ -1721,6 +1736,19 @@ public final class BroadcastQueue { } } + // Check that the receiver does *not* belong to any of the excluded packages + if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) { + if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) { + Slog.w(TAG, "Skipping delivery of excluded package " + + r.intent + " to " + + component.flattenToShortString() + + " excludes package " + component.getPackageName() + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + } + if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && r.requiredPermissions != null && r.requiredPermissions.length > 0) { for (int i = 0; i < r.requiredPermissions.length; i++) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 5343af25fd39..19ffc1733f3d 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -75,6 +75,7 @@ final class BroadcastRecord extends Binder { final String resolvedType; // the resolved data type final String[] requiredPermissions; // permissions the caller has required final String[] excludedPermissions; // permissions to exclude + final String[] excludedPackages; // packages to exclude final int appOp; // an app op that is associated with this broadcast final BroadcastOptions options; // BroadcastOptions supplied by caller final List receivers; // contains BroadcastFilter and ResolveInfo @@ -162,6 +163,10 @@ final class BroadcastRecord extends Binder { pw.print(prefix); pw.print("excludedPermissions="); pw.print(Arrays.toString(excludedPermissions)); } + if (excludedPackages != null && excludedPackages.length > 0) { + pw.print(prefix); pw.print("excludedPackages="); + pw.print(Arrays.toString(excludedPackages)); + } if (options != null) { pw.print(prefix); pw.print("options="); pw.println(options.toBundle()); } @@ -260,7 +265,8 @@ final class BroadcastRecord extends Binder { Intent _intent, ProcessRecord _callerApp, String _callerPackage, @Nullable String _callerFeatureId, int _callingPid, int _callingUid, boolean _callerInstantApp, String _resolvedType, - String[] _requiredPermissions, String[] _excludedPermissions, int _appOp, + String[] _requiredPermissions, String[] _excludedPermissions, + String[] _excludedPackages, int _appOp, BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts, @@ -280,6 +286,7 @@ final class BroadcastRecord extends Binder { resolvedType = _resolvedType; requiredPermissions = _requiredPermissions; excludedPermissions = _excludedPermissions; + excludedPackages = _excludedPackages; appOp = _appOp; options = _options; receivers = _receivers; @@ -321,6 +328,7 @@ final class BroadcastRecord extends Binder { resolvedType = from.resolvedType; requiredPermissions = from.requiredPermissions; excludedPermissions = from.excludedPermissions; + excludedPackages = from.excludedPackages; appOp = from.appOp; options = from.options; receivers = from.receivers; @@ -381,9 +389,10 @@ final class BroadcastRecord extends Binder { // build a new BroadcastRecord around that single-target list BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, - requiredPermissions, excludedPermissions, appOp, options, splitReceivers, resultTo, - resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId, - allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt); + requiredPermissions, excludedPermissions, excludedPackages, appOp, options, + splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, + initialSticky, userId, allowBackgroundActivityStarts, + mBackgroundActivityStartsToken, timeoutExempt); split.enqueueTime = this.enqueueTime; split.enqueueRealTime = this.enqueueRealTime; split.enqueueClockTime = this.enqueueClockTime; @@ -459,7 +468,7 @@ final class BroadcastRecord extends Binder { for (int i = 0; i < uidSize; i++) { final BroadcastRecord br = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, - requiredPermissions, excludedPermissions, appOp, options, + requiredPermissions, excludedPermissions, excludedPackages, appOp, options, uid2receiverList.valueAt(i), null /* _resultTo */, resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId, allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt); diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java index 2db3b15e719d..01735a754c83 100644 --- a/services/core/java/com/android/server/am/ComponentAliasResolver.java +++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java @@ -483,7 +483,7 @@ public class ComponentAliasResolver { @Nullable public Resolution<ResolveInfo> resolveReceiver(@NonNull Intent intent, @NonNull ResolveInfo receiver, @Nullable String resolvedType, - int packageFlags, int userId, int callingUid) { + int packageFlags, int userId, int callingUid, boolean forSend) { // Resolve this alias. final Resolution<ComponentName> resolution = resolveComponentAlias(() -> receiver.activityInfo.getComponentName()); @@ -506,8 +506,8 @@ public class ComponentAliasResolver { i.setPackage(null); i.setComponent(resolution.getTarget()); - List<ResolveInfo> resolved = pmi.queryIntentReceivers(i, - resolvedType, packageFlags, callingUid, userId); + List<ResolveInfo> resolved = pmi.queryIntentReceivers( + i, resolvedType, packageFlags, callingUid, userId, forSend); if (resolved == null || resolved.size() == 0) { // Target component not found. Slog.w(TAG, "Alias target " + target.flattenToShortString() + " not found"); diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java index 756209824614..35f91ba1169b 100644 --- a/services/core/java/com/android/server/am/PreBootBroadcaster.java +++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java @@ -124,7 +124,7 @@ public abstract class PreBootBroadcaster extends IIntentReceiver.Stub { REASON_PRE_BOOT_COMPLETED, ""); synchronized (mService) { mService.broadcastIntentLocked(null, null, null, mIntent, null, this, 0, null, null, - null, null, AppOpsManager.OP_NONE, bOptions.toBundle(), true, + null, null, null, AppOpsManager.OP_NONE, bOptions.toBundle(), true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cceacd8c6fa3..a2048a347c7c 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2028,8 +2028,10 @@ public final class ProcessList { mService.mProcessList.handlePredecessorProcDied((ProcessRecord) msg.obj); break; case MSG_PROCESS_KILL_TIMEOUT: - mService.handleProcessStartOrKillTimeoutLocked((ProcessRecord) msg.obj, - /* isKillTimeout */ true); + synchronized (mService) { + mService.handleProcessStartOrKillTimeoutLocked((ProcessRecord) msg.obj, + /* isKillTimeout */ true); + } break; } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c04377389e8e..7ffea26638f5 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -3203,8 +3203,8 @@ class UserController implements Handler.Callback { synchronized (mService) { return mService.broadcastIntentLocked(null, null, null, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions, null, - appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid, - realCallingPid, userId); + null, appOp, bOptions, ordered, sticky, callingPid, callingUid, + realCallingUid, realCallingPid, userId); } } diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index d239c02d4529..27ce493f717f 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -501,6 +501,7 @@ public final class AppHibernationService extends SystemService { null /* resultExtras */, requiredPermissions, null /* excludedPermissions */, + null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* serialized */, @@ -519,6 +520,7 @@ public final class AppHibernationService extends SystemService { null /* resultExtras */, requiredPermissions, null /* excludedPermissions */, + null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* serialized */, diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 36afb3677438..b9da144713cd 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -43,6 +43,7 @@ import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PLAY_AUDIO; +import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; @@ -2368,7 +2369,8 @@ public class AppOpsService extends IAppOpsService.Stub { ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid(); if (!isSelfRequest) { - boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid()); + boolean isCallerInstrumented = + ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID; boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); boolean isCallerPermissionController; try { @@ -3848,7 +3850,7 @@ public class AppOpsService extends IAppOpsService.Stub { // the data gated by OP_RECORD_AUDIO. // // TODO: Revert this change before Android 12. - if (code == OP_RECORD_AUDIO_HOTWORD) { + if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) { int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); if (result != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(result, code, attributionTag, packageName); @@ -6924,7 +6926,8 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() { ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); - boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid()); + boolean isCallerInstrumented = + ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID; boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); if (!isCallerSystem && !isCallerInstrumented) { return null; diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index 8de515d4d3e5..158092f33ee3 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -34,6 +34,7 @@ import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; +import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.flagsToString; import static android.app.AppOpsManager.getUidStateName; @@ -133,7 +134,7 @@ final class DiscreteRegistry { private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + "," - + OP_PHONE_CALL_CAMERA; + + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 3b715a218e5d..03dcc8d711d3 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1289,8 +1289,11 @@ import java.util.concurrent.atomic.AtomicBoolean; break; case MSG_L_SET_BT_ACTIVE_DEVICE: synchronized (mDeviceStateLock) { - mDeviceInventory.onSetBtActiveDevice((BtDeviceInfo) msg.obj, - mAudioService.getBluetoothContextualVolumeStream()); + BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; + mDeviceInventory.onSetBtActiveDevice(btInfo, + (btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput) + ? mAudioService.getBluetoothContextualVolumeStream() + : AudioSystem.STREAM_DEFAULT); } break; case MSG_BT_HEADSET_CNCT_FAILED: diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index aed63ce5b2c6..0123c646c764 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4146,19 +4146,8 @@ public class AudioService extends IAudioService.Stub { streamType = mStreamVolumeAlias[streamType]; - if (streamType == AudioSystem.STREAM_MUSIC) { - flags = updateFlagsForTvPlatform(flags); - synchronized (mHdmiClientLock) { - // Don't display volume UI on a TV Playback device when using absolute volume - if (mHdmiCecVolumeControlEnabled && mHdmiPlaybackClient != null - && (isAbsoluteVolumeDevice(device) - || isA2dpAbsoluteVolumeDevice(device))) { - flags &= ~AudioManager.FLAG_SHOW_UI; - } - } - if (isFullVolumeDevice(device)) { - flags &= ~AudioManager.FLAG_SHOW_UI; - } + if (streamType == AudioSystem.STREAM_MUSIC && isFullVolumeDevice(device)) { + flags &= ~AudioManager.FLAG_SHOW_UI; } mVolumeController.postVolumeChanged(streamType, flags); } @@ -6648,6 +6637,11 @@ public class AudioService extends IAudioService.Stub // verify arguments Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + sVolumeLogger.log(new AudioEventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" + + device.getAddress() + " behavior:" + + AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior) + + " pack:" + pkgName).printLog(TAG)); if (pkgName == null) { pkgName = ""; } @@ -8363,7 +8357,7 @@ public class AudioService extends IAudioService.Stub private void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported sVolumeLogger.log(new AudioEventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr=" - + address + " support=" + support)); + + address + " support=" + support).printLog(TAG)); mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support); setAvrcpAbsoluteVolumeSupported(support); } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index d8aa9aa6591e..0b9cb1977643 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -70,12 +70,6 @@ public class SpatializerHelper { private @Nullable SensorManager mSensorManager; //------------------------------------------------------------ - /** head tracker sensor name */ - // TODO: replace with generic head tracker sensor name. - // the current implementation refers to the "google" namespace but will be replaced - // by an android name at the next API level revision, it is not Google-specific. - private static final String HEADTRACKER_SENSOR = - "com.google.hardware.sensor.hid_dynamic.headtracker"; private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) { { @@ -125,6 +119,8 @@ public class SpatializerHelper { private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private boolean mTransauralSupported = false; private boolean mBinauralSupported = false; + private boolean mIsHeadTrackingSupported = false; + private int[] mSupportedHeadTrackingModes = new int[0]; private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; private boolean mHeadTrackerAvailable = false; @@ -137,9 +133,9 @@ public class SpatializerHelper { private int mSpatOutput = 0; private @Nullable ISpatializer mSpat; private @Nullable SpatializerCallback mSpatCallback; - private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback; + private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback = + new SpatializerHeadTrackingCallback(); private @Nullable HelperDynamicSensorCallback mDynSensorCallback; - private boolean mIsHeadTrackingSupported = false; // default attributes and format that determine basic availability of spatialization private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() @@ -195,6 +191,8 @@ public class SpatializerHelper { return; } // capabilities of spatializer? + resetCapabilities(); + try { byte[] levels = spat.getSupportedLevels(); if (levels == null @@ -213,6 +211,38 @@ public class SpatializerHelper { break; } } + + // Note: head tracking support must be initialized before spatialization modes as + // addCompatibleAudioDevice() calls onRoutingUpdated() which will initialize the + // sensors according to mIsHeadTrackingSupported. + mIsHeadTrackingSupported = spat.isHeadTrackingSupported(); + if (mIsHeadTrackingSupported) { + final byte[] values = spat.getSupportedHeadTrackingModes(); + ArrayList<Integer> list = new ArrayList<>(0); + for (byte value : values) { + switch (value) { + case SpatializerHeadTrackingMode.OTHER: + case SpatializerHeadTrackingMode.DISABLED: + // not expected here, skip + break; + case SpatializerHeadTrackingMode.RELATIVE_WORLD: + case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + list.add(headTrackingModeTypeToSpatializerInt(value)); + break; + default: + Log.e(TAG, "Unexpected head tracking mode:" + value, + new IllegalArgumentException("invalid mode")); + break; + } + } + mSupportedHeadTrackingModes = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + mSupportedHeadTrackingModes[i] = list.get(i); + } + mActualHeadTrackingMode = + headTrackingModeTypeToSpatializerInt(spat.getActualHeadTrackingMode()); + } + byte[] spatModes = spat.getSupportedModes(); for (byte mode : spatModes) { switch (mode) { @@ -258,7 +288,7 @@ public class SpatializerHelper { } // TODO read persisted states } catch (RemoteException e) { - /* capable level remains at NONE*/ + resetCapabilities(); } finally { if (spat != null) { try { @@ -285,12 +315,19 @@ public class SpatializerHelper { releaseSpat(); mState = STATE_UNINITIALIZED; mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; - mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; init(true); setSpatializerEnabledInt(featureEnabled); } + private void resetCapabilities() { + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mBinauralSupported = false; + mTransauralSupported = false; + mIsHeadTrackingSupported = false; + mSupportedHeadTrackingModes = new int[0]; + } + //------------------------------------------------------ // routing monitoring synchronized void onRoutingUpdated() { @@ -852,18 +889,19 @@ public class SpatializerHelper { private void createSpat() { if (mSpat == null) { mSpatCallback = new SpatializerCallback(); - mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback(); mSpat = AudioSystem.getSpatializer(mSpatCallback); try { - mIsHeadTrackingSupported = mSpat.isHeadTrackingSupported(); //TODO: register heatracking callback only when sensors are registered if (mIsHeadTrackingSupported) { + mActualHeadTrackingMode = + headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback); } } catch (RemoteException e) { Log.e(TAG, "Can't configure head tracking", e); mState = STATE_NOT_SUPPORTED; mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; } } } @@ -883,7 +921,6 @@ public class SpatializerHelper { } catch (RemoteException e) { Log.e(TAG, "Can't set release spatializer cleanly", e); } - mIsHeadTrackingSupported = false; mSpat = null; } } @@ -951,76 +988,11 @@ public class SpatializerHelper { } synchronized int[] getSupportedHeadTrackingModes() { - switch (mState) { - case STATE_UNINITIALIZED: - return new int[0]; - case STATE_NOT_SUPPORTED: - // return an empty list when Spatializer functionality is not supported - // because the list of head tracking modes you can set is actually empty - // as defined in {@link Spatializer#getSupportedHeadTrackingModes()} - return new int[0]; - case STATE_ENABLED_UNAVAILABLE: - case STATE_DISABLED_UNAVAILABLE: - case STATE_DISABLED_AVAILABLE: - case STATE_ENABLED_AVAILABLE: - if (mSpat == null) { - return new int[0]; - } - break; - } - // mSpat != null - try { - final byte[] values = mSpat.getSupportedHeadTrackingModes(); - ArrayList<Integer> list = new ArrayList<>(0); - for (byte value : values) { - switch (value) { - case SpatializerHeadTrackingMode.OTHER: - case SpatializerHeadTrackingMode.DISABLED: - // not expected here, skip - break; - case SpatializerHeadTrackingMode.RELATIVE_WORLD: - case SpatializerHeadTrackingMode.RELATIVE_SCREEN: - list.add(headTrackingModeTypeToSpatializerInt(value)); - break; - default: - Log.e(TAG, "Unexpected head tracking mode:" + value, - new IllegalArgumentException("invalid mode")); - break; - } - } - int[] modes = new int[list.size()]; - for (int i = 0; i < list.size(); i++) { - modes[i] = list.get(i); - } - return modes; - } catch (RemoteException e) { - Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e); - return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED }; - } + return mSupportedHeadTrackingModes; } synchronized int getActualHeadTrackingMode() { - switch (mState) { - case STATE_UNINITIALIZED: - return Spatializer.HEAD_TRACKING_MODE_DISABLED; - case STATE_NOT_SUPPORTED: - return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - case STATE_ENABLED_UNAVAILABLE: - case STATE_DISABLED_UNAVAILABLE: - case STATE_DISABLED_AVAILABLE: - case STATE_ENABLED_AVAILABLE: - if (mSpat == null) { - return Spatializer.HEAD_TRACKING_MODE_DISABLED; - } - break; - } - // mSpat != null - try { - return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling getActualHeadTrackingMode", e); - return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - } + return mActualHeadTrackingMode; } synchronized int getDesiredHeadTrackingMode() { @@ -1465,19 +1437,19 @@ public class SpatializerHelper { pw.println("\tmState:" + mState); pw.println("\tmSpatLevel:" + mSpatLevel); pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel); - pw.println("\tmActualHeadTrackingMode:" - + Spatializer.headtrackingModeToString(mActualHeadTrackingMode)); - pw.println("\tmDesiredHeadTrackingMode:" - + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode)); - pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:" - + mTransauralSupported); + pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported); StringBuilder modesString = new StringBuilder(); - int[] modes = getSupportedHeadTrackingModes(); - for (int mode : modes) { + for (int mode : mSupportedHeadTrackingModes) { modesString.append(Spatializer.headtrackingModeToString(mode)).append(" "); } pw.println("\tsupported head tracking modes:" + modesString); + pw.println("\tmDesiredHeadTrackingMode:" + + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode)); + pw.println("\tmActualHeadTrackingMode:" + + Spatializer.headtrackingModeToString(mActualHeadTrackingMode)); pw.println("\theadtracker available:" + mHeadTrackerAvailable); + pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:" + + mTransauralSupported); pw.println("\tmSpatOutput:" + mSpatOutput); pw.println("\tdevices:"); for (SADeviceState device : mSADevices) { @@ -1544,24 +1516,24 @@ public class SpatializerHelper { private int getHeadSensorHandleUpdateTracker() { int headHandle = -1; UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]); - List<Sensor> sensors = new ArrayList<Sensor>(0); - sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER)); - sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_DEVICE_PRIVATE_BASE)); + // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion + // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR + // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by + // SensorPoseProvider). + // Note: this is a dynamic sensor list right now. + List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER); for (Sensor sensor : sensors) { - if (sensor.getType() == Sensor.TYPE_HEAD_TRACKER - || sensor.getStringType().equals(HEADTRACKER_SENSOR)) { - UUID uuid = sensor.getUuid(); - if (uuid.equals(routingDeviceUuid)) { - headHandle = sensor.getHandle(); - if (!setHasHeadTracker(ROUTING_DEVICES[0])) { - headHandle = -1; - } - break; - } - if (uuid.equals(UuidUtils.STANDALONE_UUID)) { - headHandle = sensor.getHandle(); - break; + final UUID uuid = sensor.getUuid(); + if (uuid.equals(routingDeviceUuid)) { + headHandle = sensor.getHandle(); + if (!setHasHeadTracker(ROUTING_DEVICES[0])) { + headHandle = -1; } + break; + } + if (uuid.equals(UuidUtils.STANDALONE_UUID)) { + headHandle = sensor.getHandle(); + // we do not break, perhaps we find a head tracker on device. } } return headHandle; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index de0a36a32b66..bf7a62aadc28 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -88,6 +88,10 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem mCallback.onClientFinished(this, true /* success */); } + public boolean interruptsPrecedingClients() { + return true; + } + /** * Reset the local lockout state and notify any listeners. * diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java index 6e74d3622c1a..f29b9e823109 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java @@ -65,6 +65,10 @@ public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> { startHalOperation(); } + public boolean interruptsPrecedingClients() { + return true; + } + @Override protected void startHalOperation() { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index f90cba79dac2..c8148df9ea71 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -82,6 +82,10 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem } } + public boolean interruptsPrecedingClients() { + return true; + } + void onLockoutCleared() { resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache, mLockoutResetDispatcher); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java index 559ca0633c42..843fcc8ee2a6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java @@ -50,6 +50,10 @@ public class FingerprintResetLockoutClient extends BaseClientMonitor { callback.onClientFinished(this, true /* success */); } + public boolean interruptsPrecedingClients() { + return true; + } + @Override public int getProtoEnum() { return BiometricsProto.CM_RESET_LOCKOUT; diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java index 28c7cad3b184..c37d4c6bcaef 100644 --- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java +++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java @@ -18,6 +18,7 @@ package com.android.server.clipboard; import android.annotation.Nullable; import android.content.ClipData; +import android.os.PersistableBundle; import android.os.SystemProperties; import android.system.ErrnoException; import android.system.Os; @@ -25,6 +26,7 @@ import android.system.OsConstants; import android.system.VmSocketAddress; import android.util.Slog; +import java.io.EOFException; import java.io.FileDescriptor; import java.io.InterruptedIOException; import java.net.SocketException; @@ -93,16 +95,16 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { } } - private byte[] receiveMessage() throws ErrnoException, InterruptedIOException { + private byte[] receiveMessage() throws ErrnoException, InterruptedIOException, EOFException { final byte[] lengthBits = new byte[4]; - Os.read(mPipe, lengthBits, 0, lengthBits.length); + readFully(mPipe, lengthBits, 0, lengthBits.length); final ByteBuffer bb = ByteBuffer.wrap(lengthBits); bb.order(ByteOrder.LITTLE_ENDIAN); final int msgLen = bb.getInt(); final byte[] msg = new byte[msgLen]; - Os.read(mPipe, msg, 0, msg.length); + readFully(mPipe, msg, 0, msg.length); return msg; } @@ -115,8 +117,8 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { bb.order(ByteOrder.LITTLE_ENDIAN); bb.putInt(msg.length); - Os.write(fd, lengthBits, 0, lengthBits.length); - Os.write(fd, msg, 0, msg.length); + writeFully(fd, lengthBits, 0, lengthBits.length); + writeFully(fd, msg, 0, msg.length); } EmulatorClipboardMonitor(final Consumer<ClipData> setAndroidClipboard) { @@ -136,12 +138,15 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { final ClipData clip = new ClipData("host clipboard", new String[]{"text/plain"}, new ClipData.Item(str)); + final PersistableBundle bundle = new PersistableBundle(); + bundle.putBoolean("com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY", true); + clip.getDescription().setExtras(bundle); if (LOG_CLIBOARD_ACCESS) { Slog.i(TAG, "Setting the guest clipboard to '" + str + "'"); } setAndroidClipboard.accept(clip); - } catch (ErrnoException | InterruptedIOException e) { + } catch (ErrnoException | EOFException | InterruptedIOException e) { closePipe(); } catch (InterruptedException | IllegalArgumentException e) { } @@ -182,4 +187,32 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { t.start(); } } + + private static void readFully(final FileDescriptor fd, + final byte[] buf, int offset, int size) + throws ErrnoException, InterruptedIOException, EOFException { + while (size > 0) { + final int r = Os.read(fd, buf, offset, size); + if (r > 0) { + offset += r; + size -= r; + } else { + throw new EOFException(); + } + } + } + + private static void writeFully(final FileDescriptor fd, + final byte[] buf, int offset, int size) + throws ErrnoException, InterruptedIOException { + while (size > 0) { + final int r = Os.write(fd, buf, offset, size); + if (r > 0) { + offset += r; + size -= r; + } else { + throw new ErrnoException("write", OsConstants.EIO); + } + } + } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 68a53f13da8a..312da9a7243e 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -209,7 +209,6 @@ public class Vpn { private final NetworkInfo mNetworkInfo; private int mLegacyState; @VisibleForTesting protected String mPackage; - private String mSessionKey; private int mOwnerUID; private boolean mIsPackageTargetingAtLeastQ; @VisibleForTesting @@ -1534,11 +1533,17 @@ public class Vpn { } // Note: Return type guarantees results are deduped and sorted, which callers require. + // This method also adds the SDK sandbox UIDs corresponding to the applications by default, + // since apps are generally not aware of them, yet they should follow the VPN configuration + // of the app they belong to. private SortedSet<Integer> getAppsUids(List<String> packageNames, int userId) { SortedSet<Integer> uids = new TreeSet<>(); for (String app : packageNames) { int uid = getAppUid(app, userId); if (uid != -1) uids.add(uid); + if (Process.isApplicationUid(uid)) { + uids.add(Process.toSdkSandboxUid(uid)); + } } return uids; } @@ -1991,9 +1996,7 @@ public class Vpn { public synchronized int getActiveVpnType() { if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE; if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE; - return mVpnRunner instanceof IkeV2VpnRunner - ? VpnManager.TYPE_VPN_PLATFORM - : VpnManager.TYPE_VPN_LEGACY; + return isIkev2VpnRunner() ? VpnManager.TYPE_VPN_PLATFORM : VpnManager.TYPE_VPN_LEGACY; } private void updateAlwaysOnNotification(DetailedState networkState) { @@ -2531,6 +2534,7 @@ public class Vpn { @Nullable private IpSecTunnelInterface mTunnelIface; @Nullable private IkeSession mSession; @Nullable private Network mActiveNetwork; + private final String mSessionKey; IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) { super(TAG); @@ -2876,7 +2880,6 @@ public class Vpn { */ private void disconnectVpnRunner() { mActiveNetwork = null; - mSessionKey = null; mIsRunning = false; resetIkeState(); @@ -3306,7 +3309,7 @@ public class Vpn { } private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) { - return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner; + return isCurrentPreparedPackage(packageName) && isIkev2VpnRunner(); } /** @@ -3360,6 +3363,16 @@ public class Vpn { return VpnProfile.decode("" /* Key unused */, encoded); } + private boolean isIkev2VpnRunner() { + return (mVpnRunner instanceof IkeV2VpnRunner); + } + + @GuardedBy("this") + @Nullable + private String getSessionKeyLocked() { + return isIkev2VpnRunner() ? ((IkeV2VpnRunner) mVpnRunner).mSessionKey : null; + } + /** * Starts an already provisioned VPN Profile, keyed by package name. * @@ -3387,7 +3400,11 @@ public class Vpn { } startVpnProfilePrivileged(profile, packageName); - return mSessionKey; + if (!isIkev2VpnRunner()) { + throw new IllegalStateException("mVpnRunner shouldn't be null and should also be " + + "an instance of Ikev2VpnRunner"); + } + return getSessionKeyLocked(); } finally { Binder.restoreCallingIdentity(token); } @@ -3490,11 +3507,8 @@ public class Vpn { } private VpnProfileState makeVpnProfileState() { - // TODO: mSessionKey will be moved to Ikev2VpnRunner once aosp/2007077 is merged, so after - // merging aosp/2007077, here should check Ikev2VpnRunner is null or not. Session key will - // be null if Ikev2VpnRunner is null. - return new VpnProfileState(getStateFromLegacyState(mLegacyState), mSessionKey, mAlwaysOn, - mLockdown); + return new VpnProfileState(getStateFromLegacyState(mLegacyState), + isIkev2VpnRunner() ? getSessionKeyLocked() : null, mAlwaysOn, mLockdown); } /** diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 9824b4e6c43a..f8a74f4f3f55 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4176,7 +4176,11 @@ public class HdmiControlService extends SystemService { List<AudioDeviceAttributes> streamMusicDevices = getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); if (streamMusicDevices.contains(getAvcAudioOutputDevice())) { - setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME); + int flags = AudioManager.FLAG_ABSOLUTE_VOLUME; + if (isTvDevice()) { + flags |= AudioManager.FLAG_SHOW_UI; + } + setStreamMusicVolume(volume, flags); } } @@ -4190,8 +4194,11 @@ public class HdmiControlService extends SystemService { getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); if (streamMusicDevices.contains(getAvcAudioOutputDevice())) { int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE; - getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, - AudioManager.FLAG_ABSOLUTE_VOLUME); + int flags = AudioManager.FLAG_ABSOLUTE_VOLUME; + if (isTvDevice()) { + flags |= AudioManager.FLAG_SHOW_UI; + } + getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, flags); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index e4e9d1d49a6e..b624d438a4f7 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -20,6 +20,7 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManagerInternal; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -860,6 +861,19 @@ public class InputManagerService extends IInputManager.Stub @Override // Binder call public boolean injectInputEvent(InputEvent event, int mode) { + return injectInputEventToTarget(event, mode, Process.INVALID_UID); + } + + @Override // Binder call + public boolean injectInputEventToTarget(InputEvent event, int mode, int targetUid) { + if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS, + "injectInputEvent()", true /*checkInstrumentationSource*/)) { + throw new SecurityException( + "Injecting input events requires the caller (or the source of the " + + "instrumentation, if any) to have the INJECT_EVENTS permission."); + } + // We are not checking if targetUid matches the callingUid, since having the permission + // already means you can inject into any window. Objects.requireNonNull(event, "event must not be null"); if (mode != InputEventInjectionSync.NONE && mode != InputEventInjectionSync.WAIT_FOR_FINISHED @@ -868,22 +882,39 @@ public class InputManagerService extends IInputManager.Stub } final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); + final boolean injectIntoUid = targetUid != Process.INVALID_UID; final int result; try { - result = mNative.injectInputEvent(event, pid, uid, mode, - INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); + result = mNative.injectInputEvent(event, injectIntoUid, + targetUid, mode, INJECTION_TIMEOUT_MILLIS, + WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); } finally { Binder.restoreCallingIdentity(ident); } switch (result) { - case InputEventInjectionResult.PERMISSION_DENIED: - Slog.w(TAG, "Input event injection from pid " + pid + " permission denied."); - throw new SecurityException( - "Injecting to another application requires INJECT_EVENTS permission"); case InputEventInjectionResult.SUCCEEDED: return true; + case InputEventInjectionResult.TARGET_MISMATCH: + if (!injectIntoUid) { + throw new IllegalStateException("Injection should not result in TARGET_MISMATCH" + + " when it is not targeted into to a specific uid."); + } + // TODO(b/228161340): Remove the fallback of targeting injection into all windows + // when the caller has the injection permission. + // Explicitly maintain the same behavior as previous versions of Android, where + // injection is allowed into all windows if the caller has the INJECT_EVENTS + // permission, even if it is targeting a certain uid. + if (checkCallingPermission(android.Manifest.permission.INJECT_EVENTS, + "injectInputEvent-target-mismatch-fallback")) { + Slog.w(TAG, "Targeted input event was not directed at a window owned by uid " + + targetUid + ". Falling back to injecting into all windows."); + return injectInputEventToTarget(event, mode, Process.INVALID_UID); + } + throw new IllegalArgumentException( + "Targeted input event injection from pid " + pid + + " was not directed at a window owned by uid " + + targetUid + "."); case InputEventInjectionResult.TIMED_OUT: Slog.w(TAG, "Input event injection from pid " + pid + " timed out."); return false; @@ -2780,8 +2811,12 @@ public class InputManagerService extends IInputManager.Stub } } } - private boolean checkCallingPermission(String permission, String func) { + return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/); + } + + private boolean checkCallingPermission(String permission, String func, + boolean checkInstrumentationSource) { // Quick check: if the calling permission is me, it's all okay. if (Binder.getCallingPid() == Process.myPid()) { return true; @@ -2790,6 +2825,28 @@ public class InputManagerService extends IInputManager.Stub if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) { return true; } + + if (checkInstrumentationSource) { + final ActivityManagerInternal ami = + LocalServices.getService(ActivityManagerInternal.class); + Objects.requireNonNull(ami, "ActivityManagerInternal should not be null."); + final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid()); + if (instrumentationUid != Process.INVALID_UID) { + // Clear the calling identity when checking if the instrumentation source has + // permission because PackageManager will deny all permissions to some callers, + // such as instant apps. + final long token = Binder.clearCallingIdentity(); + try { + if (mContext.checkPermission(permission, -1 /*pid*/, instrumentationUid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -3035,13 +3092,6 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") - private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) { - return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS, - injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED; - } - - // Native callback. - @SuppressWarnings("unused") private void onPointerDownOutsideFocus(IBinder touchedToken) { mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken); } @@ -3501,12 +3551,17 @@ public class InputManagerService extends IInputManager.Stub @Override public void sendInputEvent(InputEvent event, int policyFlags) { + if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS, + "sendInputEvent()")) { + throw new SecurityException( + "The INJECT_EVENTS permission is required for injecting input events."); + } Objects.requireNonNull(event, "event must not be null"); synchronized (mInputFilterLock) { if (!mDisconnected) { - mNative.injectInputEvent(event, 0, 0, - InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0, + mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */, policyFlags | WindowManagerPolicy.FLAG_FILTERED); } } diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 81882d277a99..9cf80737e67d 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -70,7 +70,18 @@ public interface NativeInputManagerService { void setBlockUntrustedTouchesMode(int mode); - int injectInputEvent(InputEvent event, int pid, int uid, int syncMode, + /** + * Inject an input event into the system. + * + * @param event the input event to inject + * @param injectIntoUid true if the event should target windows owned by uid, false otherwise + * @param uid the uid whose windows should be targeted, if any + * @param syncMode {@link android.os.InputEventInjectionSync} + * @param timeoutMillis timeout to wait for input injection to complete, in milliseconds + * @param policyFlags defined in {@link android.view.WindowManagerPolicyConstants} + * @return {@link android.os.InputEventInjectionResult} + */ + int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, int syncMode, int timeoutMillis, int policyFlags); VerifiedInputEvent verifyInputEvent(InputEvent event); @@ -240,7 +251,8 @@ public interface NativeInputManagerService { public native void setBlockUntrustedTouchesMode(int mode); @Override - public native int injectInputEvent(InputEvent event, int pid, int uid, int syncMode, + public native int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, + int syncMode, int timeoutMillis, int policyFlags); @Override diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index ea99e7972887..f5c2bbc8d5a2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -1587,6 +1587,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (isGpsEnabled()) { setGpsEnabled(false); updateEnabled(); + restartLocationRequest(); } } diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index 21beb964529a..1bcc21e66302 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -410,7 +410,8 @@ public final class LogcatManagerService extends SystemService { } private void processNewLogAccessRequest(LogAccessClient client) { - boolean isInstrumented = mActivityManagerInternal.isUidCurrentlyInstrumented(client.mUid); + boolean isInstrumented = mActivityManagerInternal.getInstrumentationSourceUid(client.mUid) + != android.os.Process.INVALID_UID; // The instrumented apks only run for testing, so we don't check user permission. if (isInstrumented) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 83c576e9259d..0eda980a29b6 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -58,6 +58,7 @@ import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.BIND_NOT_PERCEPTIBLE; import static android.content.pm.PackageManager.FEATURE_LEANBACK; +import static android.content.pm.PackageManager.FEATURE_TELECOM; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -655,10 +656,9 @@ public class NotificationManagerService extends SystemService { private int mWarnRemoteViewsSizeBytes; private int mStripRemoteViewsSizeBytes; - final boolean mEnableAppSettingMigration; - private boolean mForceUserSetOnUpgrade; private MetricsLogger mMetricsLogger; + private NotificationChannelLogger mNotificationChannelLogger; private TriPredicate<String, Integer, String> mAllowedManagedServicePackages; private final SavePolicyFileRunnable mSavePolicyFile = new SavePolicyFileRunnable(); @@ -1998,12 +1998,6 @@ public class NotificationManagerService extends SystemService { mNotificationRecordLogger = notificationRecordLogger; mNotificationInstanceIdSequence = notificationInstanceIdSequence; Notification.processAllowlistToken = ALLOWLIST_TOKEN; - // TODO (b/194833441): remove when OS is ready for migration. This flag is checked once - // rather than having a settings observer because some of the behaviors (e.g. readXml) only - // happen on reboot - mEnableAppSettingMigration = Settings.Secure.getIntForUser( - getContext().getContentResolver(), - Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1; } // TODO - replace these methods with new fields in the VisibleForTesting constructor @@ -2161,6 +2155,11 @@ public class NotificationManagerService extends SystemService { mAccessibilityManager = am; } + @VisibleForTesting + void setTelecomManager(TelecomManager tm) { + mTelecomManager = tm; + } + // TODO: All tests should use this init instead of the one-off setters above. @VisibleForTesting void init(WorkerHandler handler, RankingHandler rankingHandler, @@ -2178,7 +2177,7 @@ public class NotificationManagerService extends SystemService { TelephonyManager telephonyManager, ActivityManagerInternal ami, MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper, UsageStatsManagerInternal usageStatsManagerInternal, - TelecomManager telecomManager) { + TelecomManager telecomManager, NotificationChannelLogger channelLogger) { mHandler = handler; Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), @@ -2275,14 +2274,16 @@ public class NotificationManagerService extends SystemService { } }); mPermissionHelper = permissionHelper; + mNotificationChannelLogger = channelLogger; mPreferencesHelper = new PreferencesHelper(getContext(), mPackageManagerClient, mRankingHandler, mZenModeHelper, mPermissionHelper, - new NotificationChannelLoggerImpl(), + mNotificationChannelLogger, mAppOps, new SysUiStatsEvent.BuilderFactory()); + mPreferencesHelper.updateFixedImportance(mUm.getUsers()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, @@ -2362,9 +2363,6 @@ public class NotificationManagerService extends SystemService { mNotificationEffectsEnabledForAutomotive = resources.getBoolean(R.bool.config_enableServerNotificationEffectsForAutomotive); - mPreferencesHelper.lockChannelsForOEM(getContext().getResources().getStringArray( - com.android.internal.R.array.config_nonBlockableNotificationPackages)); - mZenModeHelper.setPriorityOnlyDndExemptPackages(getContext().getResources().getStringArray( com.android.internal.R.array.config_priorityOnlyDndExemptPackages)); @@ -2473,9 +2471,6 @@ public class NotificationManagerService extends SystemService { WorkerHandler handler = new WorkerHandler(Looper.myLooper()); - mForceUserSetOnUpgrade = getContext().getResources().getBoolean( - R.bool.config_notificationForceUserSetOnUpgrade); - init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), AppGlobals.getPackageManager(), getContext().getPackageManager(), getLocalService(LightsManager.class), @@ -2504,10 +2499,10 @@ public class NotificationManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class), createToastRateLimiter(), new PermissionHelper(LocalServices.getService( PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(), - AppGlobals.getPermissionManager(), mEnableAppSettingMigration, - mForceUserSetOnUpgrade), + AppGlobals.getPermissionManager()), LocalServices.getService(UsageStatsManagerInternal.class), - getContext().getSystemService(TelecomManager.class)); + getContext().getSystemService(TelecomManager.class), + new NotificationChannelLoggerImpl()); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL); @@ -2799,7 +2794,7 @@ public class NotificationManagerService extends SystemService { } } - private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, + void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, boolean fromListener) { if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { // cancel @@ -2821,11 +2816,9 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true); mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true); - if (mEnableAppSettingMigration) { - if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) { - mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid), - channel.getImportance() != IMPORTANCE_NONE, true); - } + if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) { + mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid), + channel.getImportance() != IMPORTANCE_NONE, true); } maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); @@ -3474,36 +3467,19 @@ public class NotificationManagerService extends SystemService { @Override public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { enforceSystemOrSystemUI("setNotificationsEnabledForPackage"); - if (mEnableAppSettingMigration) { - boolean wasEnabled = mPermissionHelper.hasPermission(uid); - if (wasEnabled == enabled) { - return; - } - mPermissionHelper.setNotificationPermission( - pkg, UserHandle.getUserId(uid), enabled, true); - sendAppBlockStateChangedBroadcast(pkg, uid, !enabled); - } else { - synchronized (mNotificationLock) { - boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid) - != NotificationManager.IMPORTANCE_NONE; - - if (wasEnabled == enabled) { - return; - } - } - - mPreferencesHelper.setEnabled(pkg, uid, enabled); - // TODO (b/194833441): this is being ignored by app ops now that the permission - // exists, so send the broadcast manually - mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg, - enabled ? MODE_ALLOWED : AppOpsManager.MODE_IGNORED); - - sendAppBlockStateChangedBroadcast(pkg, uid, !enabled); + boolean wasEnabled = mPermissionHelper.hasPermission(uid); + if (wasEnabled == enabled) { + return; } + mPermissionHelper.setNotificationPermission( + pkg, UserHandle.getUserId(uid), enabled, true); + sendAppBlockStateChangedBroadcast(pkg, uid, !enabled); + mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES) .setType(MetricsEvent.TYPE_ACTION) .setPackageName(pkg) .setSubtype(enabled ? 1 : 0)); + mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled); // Now, cancel any outstanding notifications that are part of a just-disabled app if (!enabled) { cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true, @@ -3529,8 +3505,6 @@ public class NotificationManagerService extends SystemService { public void setNotificationsEnabledWithImportanceLockForPackage( String pkg, int uid, boolean enabled) { setNotificationsEnabledForPackage(pkg, uid, enabled); - - mPreferencesHelper.setAppImportanceLocked(pkg, uid); } /** @@ -3646,18 +3620,20 @@ public class NotificationManagerService extends SystemService { @Override public int getPackageImportance(String pkg) { checkCallerIsSystemOrSameApp(pkg); - if (mEnableAppSettingMigration) { - if (mPermissionHelper.hasPermission(Binder.getCallingUid())) { - return IMPORTANCE_DEFAULT; - } else { - return IMPORTANCE_NONE; - } + if (mPermissionHelper.hasPermission(Binder.getCallingUid())) { + return IMPORTANCE_DEFAULT; } else { - return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid()); + return IMPORTANCE_NONE; } } @Override + public boolean isImportanceLocked(String pkg, int uid) { + checkCallerIsSystem(); + return mPreferencesHelper.isImportanceLocked(pkg, uid); + } + + @Override public boolean canShowBadge(String pkg, int uid) { checkCallerIsSystem(); return mPreferencesHelper.canShowBadge(pkg, uid); @@ -5885,8 +5861,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey, boolean needsOngoingFlag) { NotificationRecord summaryRecord = null; - boolean isPermissionFixed = mPermissionHelper.isMigrationEnabled() - ? mPermissionHelper.isPermissionFixed(pkg, userId) : false; + boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId); synchronized (mNotificationLock) { NotificationRecord notificationRecord = mNotificationsByKey.get(triggeringKey); if (notificationRecord == null) { @@ -5895,10 +5870,6 @@ public class NotificationManagerService extends SystemService { return null; } NotificationChannel channel = notificationRecord.getChannel(); - boolean isImportanceFixed = mPermissionHelper.isMigrationEnabled() - ? isPermissionFixed - : (channel.isImportanceLockedByOEM() - || channel.isImportanceLockedByCriticalDeviceFunction()); final StatusBarNotification adjustedSbn = notificationRecord.getSbn(); userId = adjustedSbn.getUser().getIdentifier(); int uid = adjustedSbn.getUid(); @@ -5944,7 +5915,7 @@ public class NotificationManagerService extends SystemService { System.currentTimeMillis()); summaryRecord = new NotificationRecord(getContext(), summarySbn, notificationRecord.getChannel()); - summaryRecord.setImportanceFixed(isImportanceFixed); + summaryRecord.setImportanceFixed(isPermissionFixed); summaryRecord.setIsAppImportanceLocked( notificationRecord.getIsAppImportanceLocked()); summaries.put(pkg, summarySbn.getKey()); @@ -5992,10 +5963,6 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> getAllUsersNotificationPermissions() { - // don't bother if migration is not enabled - if (!mEnableAppSettingMigration) { - return null; - } ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> allPermissions = new ArrayMap<>(); final List<UserInfo> allUsers = mUm.getUsers(); // for each of these, get the package notification permissions that are associated @@ -6181,7 +6148,6 @@ public class NotificationManagerService extends SystemService { pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate); pw.println(" hideSilentStatusBar=" + mPreferencesHelper.shouldHideSilentStatusIcons()); - pw.println(" mForceUserSetOnUpgrade=" + mForceUserSetOnUpgrade); } pw.println(" mArchive=" + mArchive.toString()); mArchive.dumpImpl(pw, filter); @@ -6509,13 +6475,8 @@ public class NotificationManagerService extends SystemService { + ", notificationUid=" + notificationUid + ", notification=" + notification; Slog.e(TAG, noChannelStr); - boolean appNotificationsOff; - if (mEnableAppSettingMigration) { - appNotificationsOff = !mPermissionHelper.hasPermission(notificationUid); - } else { - appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid) - == NotificationManager.IMPORTANCE_NONE; - } + boolean appNotificationsOff = !mPermissionHelper.hasPermission(notificationUid); + if (!appNotificationsOff) { doChannelWarningToast(notificationUid, @@ -6527,14 +6488,11 @@ public class NotificationManagerService extends SystemService { } final NotificationRecord r = new NotificationRecord(getContext(), n, channel); - r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid)); + r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId)); r.setPostSilently(postSilently); r.setFlagBubbleRemoved(false); r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg)); - boolean isImportanceFixed = mPermissionHelper.isMigrationEnabled() - ? mPermissionHelper.isPermissionFixed(pkg, userId) - : (channel.isImportanceLockedByOEM() - || channel.isImportanceLockedByCriticalDeviceFunction()); + boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId); r.setImportanceFixed(isImportanceFixed); if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { @@ -6978,19 +6936,20 @@ public class NotificationManagerService extends SystemService { private boolean isCallNotification(String pkg, int uid) { final long identity = Binder.clearCallingIdentity(); try { - return mTelecomManager.isInManagedCall() || mTelecomManager.isInSelfManagedCall( - pkg, UserHandle.getUserHandleForUid(uid)); + if (mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM) + && mTelecomManager != null) { + return mTelecomManager.isInManagedCall() + || mTelecomManager.isInSelfManagedCall( + pkg, UserHandle.getUserHandleForUid(uid)); + } + return false; } finally { Binder.restoreCallingIdentity(identity); } } private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) { - if (mEnableAppSettingMigration) { - return mPermissionHelper.hasPermission(uid); - } else { - return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE; - } + return mPermissionHelper.hasPermission(uid); } protected int getNotificationCount(String pkg, int userId, int excludedId, @@ -7405,6 +7364,7 @@ public class NotificationManagerService extends SystemService { @Override public void run() { boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid); + boolean isCallNotification = isCallNotification(pkg, uid); synchronized (mNotificationLock) { try { NotificationRecord r = null; @@ -7423,8 +7383,10 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification n = r.getSbn(); final Notification notification = n.getNotification(); + boolean isCallNotificationAndCorrectStyle = isCallNotification + && notification.isStyle(Notification.CallStyle.class); - if (!notification.isMediaNotification() + if (!(notification.isMediaNotification() || isCallNotificationAndCorrectStyle) && (appBanned || isRecordBlockedLocked(r))) { mUsageStats.registerBlocked(r); if (DBG) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index f979343248f1..cbaf485c077f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1089,7 +1089,7 @@ public final class NotificationRecord { } /** - * @see PreferencesHelper#getIsAppImportanceLocked(String, int) + * @see PermissionHelper#isPermissionUserSet(String, int) */ public boolean getIsAppImportanceLocked() { return mIsAppImportanceLocked; diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index b4230c11bcab..a28547b1cda9 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -55,22 +55,12 @@ public final class PermissionHelper { private final PermissionManagerServiceInternal mPmi; private final IPackageManager mPackageManager; private final IPermissionManager mPermManager; - // TODO (b/194833441): Remove when the migration is enabled - private final boolean mMigrationEnabled; - private final boolean mForceUserSetOnUpgrade; public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager, - IPermissionManager permManager, boolean migrationEnabled, - boolean forceUserSetOnUpgrade) { + IPermissionManager permManager) { mPmi = pmi; mPackageManager = packageManager; mPermManager = permManager; - mMigrationEnabled = migrationEnabled; - mForceUserSetOnUpgrade = forceUserSetOnUpgrade; - } - - public boolean isMigrationEnabled() { - return mMigrationEnabled; } /** @@ -78,7 +68,6 @@ public final class PermissionHelper { * with a lock held. */ public boolean hasPermission(int uid) { - assertFlag(); final long callingId = Binder.clearCallingIdentity(); try { return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid) @@ -93,7 +82,6 @@ public final class PermissionHelper { * Must not be called with a lock held. Format: uid, packageName */ Set<Pair<Integer, String>> getAppsRequestingPermission(int userId) { - assertFlag(); Set<Pair<Integer, String>> requested = new HashSet<>(); List<PackageInfo> pkgs = getInstalledPackages(userId); for (PackageInfo pi : pkgs) { @@ -131,7 +119,6 @@ public final class PermissionHelper { * with a lock held. Format: uid, packageName. */ Set<Pair<Integer, String>> getAppsGrantedPermission(int userId) { - assertFlag(); Set<Pair<Integer, String>> granted = new HashSet<>(); ParceledListSlice<PackageInfo> parceledList = null; try { @@ -153,7 +140,6 @@ public final class PermissionHelper { public @NonNull ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> getNotificationPermissionValues(int userId) { - assertFlag(); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>(); Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId); Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId); @@ -180,7 +166,6 @@ public final class PermissionHelper { */ public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant, boolean userSet, boolean reviewRequired) { - assertFlag(); final long callingId = Binder.clearCallingIdentity(); try { // Do not change the permission if the package doesn't request it, do not change fixed @@ -221,19 +206,16 @@ public final class PermissionHelper { * restoring a pre-T backup on a T+ device */ public void setNotificationPermission(PackagePermission pkgPerm) { - assertFlag(); if (pkgPerm == null || pkgPerm.packageName == null) { return; } if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) { - boolean userSet = mForceUserSetOnUpgrade ? true : pkgPerm.userModifiedSettings; setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, - userSet, !userSet); + true /* userSet always true on upgrade */); } } public boolean isPermissionFixed(String packageName, @UserIdInt int userId) { - assertFlag(); final long callingId = Binder.clearCallingIdentity(); try { try { @@ -251,7 +233,6 @@ public final class PermissionHelper { } boolean isPermissionUserSet(String packageName, @UserIdInt int userId) { - assertFlag(); final long callingId = Binder.clearCallingIdentity(); try { try { @@ -269,7 +250,6 @@ public final class PermissionHelper { } boolean isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId) { - assertFlag(); final long callingId = Binder.clearCallingIdentity(); try { try { @@ -288,7 +268,6 @@ public final class PermissionHelper { private boolean packageRequestsNotificationPermission(String packageName, @UserIdInt int userId) { - assertFlag(); try { String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, userId).requestedPermissions; @@ -299,12 +278,6 @@ public final class PermissionHelper { return false; } - private void assertFlag() { - if (!mMigrationEnabled) { - throw new IllegalStateException("Method called without checking flag value"); - } - } - public static class PackagePermission { public final String packageName; public final @UserIdInt int userId; diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index ef3c770f125b..97133a56779d 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -42,8 +42,10 @@ import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.content.pm.UserInfo; import android.metrics.LogMaker; import android.os.Binder; import android.os.Build; @@ -146,7 +148,6 @@ public class PreferencesHelper implements RankingConfig { static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false; private static final boolean DEFAULT_SHOW_BADGE = true; - private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false; private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false; static final boolean DEFAULT_BUBBLES_ENABLED = true; @@ -193,8 +194,6 @@ public class PreferencesHelper implements RankingConfig { private boolean mAllowInvalidShortcuts = false; - private Map<String, List<String>> mOemLockedApps = new HashMap(); - public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, PermissionHelper permHelper, NotificationChannelLogger notificationChannelLogger, @@ -209,11 +208,7 @@ public class PreferencesHelper implements RankingConfig { mAppOps = appOpsManager; mStatsEventBuilderFactory = statsEventBuilderFactory; - if (mPermissionHelper.isMigrationEnabled()) { - XML_VERSION = 4; - } else { - XML_VERSION = 2; - } + XML_VERSION = 4; updateBadgingEnabled(); updateBubblesEnabled(); @@ -230,8 +225,7 @@ public class PreferencesHelper implements RankingConfig { final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; - boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION) - && mPermissionHelper.isMigrationEnabled(); + boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION); if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) { // make a note that we should show the notification at some point. // it shouldn't be possible for the user to already have seen it, as the XML version @@ -393,8 +387,6 @@ public class PreferencesHelper implements RankingConfig { hasUserConfiguredSettings(r)); pkgPerms.add(pkgPerm); } - } else if (!mPermissionHelper.isMigrationEnabled()) { - r.importance = appImportance; } } catch (Exception e) { Slog.w(TAG, "Failed to restore pkg", e); @@ -417,16 +409,8 @@ public class PreferencesHelper implements RankingConfig { } else { channel.populateFromXml(parser); } - if (!mPermissionHelper.isMigrationEnabled()) { - channel.setImportanceLockedByCriticalDeviceFunction( - r.defaultAppLockedImportance); - channel.setImportanceLockedByOEM(r.oemLockedImportance); - if (!channel.isImportanceLockedByOEM()) { - if (r.oemLockedChannels.contains(channel.getId())) { - channel.setImportanceLockedByOEM(true); - } - } - } + channel.setImportanceLockedByCriticalDeviceFunction( + r.defaultAppLockedImportance || r.fixedImportance); if (isShortcutOk(channel) && isDeletionOk(channel)) { r.channels.put(id, channel); @@ -499,14 +483,6 @@ public class PreferencesHelper implements RankingConfig { r.visibility = visibility; r.showBadge = showBadge; r.bubblePreference = bubblePreference; - if (mOemLockedApps.containsKey(r.pkg)) { - List<String> channels = mOemLockedApps.get(r.pkg); - if (channels == null || channels.isEmpty()) { - r.oemLockedImportance = true; - } else { - r.oemLockedChannels = channels; - } - } try { createDefaultChannelIfNeededLocked(r); @@ -604,7 +580,7 @@ public class PreferencesHelper implements RankingConfig { out.endTag(null, TAG_STATUS_ICONS); } ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>(); - if (mPermissionHelper.isMigrationEnabled() && forBackup) { + if (forBackup) { notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId); } @@ -736,28 +712,6 @@ public class PreferencesHelper implements RankingConfig { } } - /** - * Gets importance. - */ - @Override - public int getImportance(String packageName, int uid) { - synchronized (mPackagePreferences) { - return getOrCreatePackagePreferencesLocked(packageName, uid).importance; - } - } - - /** - * Returns whether the importance of the corresponding notification is user-locked and shouldn't - * be adjusted by an assistant (via means of a blocking helper, for example). For the channel - * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}. - */ - public boolean getIsAppImportanceLocked(String packageName, int uid) { - synchronized (mPackagePreferences) { - int userLockedFields = getOrCreatePackagePreferencesLocked(packageName, uid).lockedAppFields; - return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0; - } - } - @Override public boolean canShowBadge(String packageName, int uid) { synchronized (mPackagePreferences) { @@ -855,6 +809,13 @@ public class PreferencesHelper implements RankingConfig { } } + boolean isImportanceLocked(String pkg, int uid) { + synchronized (mPackagePreferences) { + PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); + return r.fixedImportance || r.defaultAppLockedImportance; + } + } + @Override public boolean isGroupBlocked(String packageName, int uid, String groupId) { if (groupId == null) { @@ -1043,16 +1004,10 @@ public class PreferencesHelper implements RankingConfig { : NotificationChannel.DEFAULT_ALLOW_BUBBLE); } clearLockedFieldsLocked(channel); - if (!mPermissionHelper.isMigrationEnabled()) { - channel.setImportanceLockedByOEM(r.oemLockedImportance); - if (!channel.isImportanceLockedByOEM()) { - if (r.oemLockedChannels.contains(channel.getId())) { - channel.setImportanceLockedByOEM(true); - } - } - channel.setImportanceLockedByCriticalDeviceFunction( - r.defaultAppLockedImportance); - } + + channel.setImportanceLockedByCriticalDeviceFunction( + r.defaultAppLockedImportance || r.fixedImportance); + if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { channel.setLockscreenVisibility( NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); @@ -1133,33 +1088,14 @@ public class PreferencesHelper implements RankingConfig { updatedChannel.unlockFields(updatedChannel.getUserLockedFields()); } - if (mPermissionHelper.isMigrationEnabled()) { - if (mPermissionHelper.isPermissionFixed(r.pkg, UserHandle.getUserId(r.uid)) - && !(channel.isBlockable() || channel.getImportance() == IMPORTANCE_NONE)) { - updatedChannel.setImportance(channel.getImportance()); - } - } else { - // no importance updates are allowed if OEM blocked it - updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM()); - if (updatedChannel.isImportanceLockedByOEM()) { - updatedChannel.setImportance(channel.getImportance()); - } - updatedChannel.setImportanceLockedByCriticalDeviceFunction( - r.defaultAppLockedImportance); - if (updatedChannel.isImportanceLockedByCriticalDeviceFunction() - && updatedChannel.getImportance() == IMPORTANCE_NONE) { - updatedChannel.setImportance(channel.getImportance()); - } + if (channel.isImportanceLockedByCriticalDeviceFunction() + && !(channel.isBlockable() || channel.getImportance() == IMPORTANCE_NONE)) { + updatedChannel.setImportance(channel.getImportance()); } r.channels.put(updatedChannel.getId(), updatedChannel); if (onlyHasDefaultChannel(pkg, uid)) { - if (!mPermissionHelper.isMigrationEnabled()) { - // copy settings to app level so they are inherited by new channels - // when the app migrates - r.importance = updatedChannel.getImportance(); - } r.priority = updatedChannel.canBypassDnd() ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT; r.visibility = updatedChannel.getLockscreenVisibility(); @@ -1328,49 +1264,21 @@ public class PreferencesHelper implements RankingConfig { mHideSilentStatusBarIcons = hide; } - public void lockChannelsForOEM(String[] appOrChannelList) { - if (mPermissionHelper.isMigrationEnabled()) { - return; - } - if (appOrChannelList == null) { - return; - } - for (String appOrChannel : appOrChannelList) { - if (!TextUtils.isEmpty(appOrChannel)) { - String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM); - if (appSplit != null && appSplit.length > 0) { - String appName = appSplit[0]; - String channelId = appSplit.length == 2 ? appSplit[1] : null; - + public void updateFixedImportance(List<UserInfo> users) { + for (UserInfo user : users) { + List<PackageInfo> packages = mPm.getInstalledPackagesAsUser( + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY), + user.getUserHandle().getIdentifier()); + for (PackageInfo pi : packages) { + boolean fixed = mPermissionHelper.isPermissionFixed( + pi.packageName, user.getUserHandle().getIdentifier()); + if (fixed) { synchronized (mPackagePreferences) { - boolean foundApp = false; - for (PackagePreferences r : mPackagePreferences.values()) { - if (r.pkg.equals(appName)) { - foundApp = true; - if (channelId == null) { - // lock all channels for the app - r.oemLockedImportance = true; - for (NotificationChannel channel : r.channels.values()) { - channel.setImportanceLockedByOEM(true); - } - } else { - NotificationChannel channel = r.channels.get(channelId); - if (channel != null) { - channel.setImportanceLockedByOEM(true); - } - // Also store the locked channels on the record, so they aren't - // temporarily lost when data is cleared on the package - r.oemLockedChannels.add(channelId); - } - } - } - if (!foundApp) { - List<String> channels = - mOemLockedApps.getOrDefault(appName, new ArrayList<>()); - if (channelId != null) { - channels.add(channelId); - } - mOemLockedApps.put(appName, channels); + PackagePreferences p = getOrCreatePackagePreferencesLocked( + pi.packageName, pi.applicationInfo.uid); + p.fixedImportance = true; + for (NotificationChannel channel : p.channels.values()) { + channel.setImportanceLockedByCriticalDeviceFunction(true); } } } @@ -1380,16 +1288,15 @@ public class PreferencesHelper implements RankingConfig { public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd) { - if (mPermissionHelper.isMigrationEnabled()) { - return; - } synchronized (mPackagePreferences) { for (PackagePreferences p : mPackagePreferences.values()) { if (userId == UserHandle.getUserId(p.uid)) { if (toRemove != null && toRemove.contains(p.pkg)) { p.defaultAppLockedImportance = false; - for (NotificationChannel channel : p.channels.values()) { - channel.setImportanceLockedByCriticalDeviceFunction(false); + if (!p.fixedImportance) { + for (NotificationChannel channel : p.channels.values()) { + channel.setImportanceLockedByCriticalDeviceFunction(false); + } } } } @@ -1802,20 +1709,8 @@ public class PreferencesHelper implements RankingConfig { } for (int i = candidatePkgs.size() - 1; i >= 0; i--) { Pair<String, Integer> app = candidatePkgs.valueAt(i); - if (mPermissionHelper.isMigrationEnabled()) { - if (!mPermissionHelper.hasPermission(app.second)) { - candidatePkgs.removeAt(i); - } - } else { - synchronized (mPackagePreferences) { - PackagePreferences r = getPackagePreferencesLocked(app.first, app.second); - if (r == null) { - continue; - } - if (r.importance == IMPORTANCE_NONE) { - candidatePkgs.removeAt(i); - } - } + if (!mPermissionHelper.hasPermission(app.second)) { + candidatePkgs.removeAt(i); } } boolean haveBypassingApps = candidatePkgs.size() > 0; @@ -1861,27 +1756,6 @@ public class PreferencesHelper implements RankingConfig { } /** - * Sets importance. - */ - @Override - public void setImportance(String pkgName, int uid, int importance) { - synchronized (mPackagePreferences) { - getOrCreatePackagePreferencesLocked(pkgName, uid).importance = importance; - } - updateConfig(); - } - - public void setEnabled(String packageName, int uid, boolean enabled) { - boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE; - if (wasEnabled == enabled) { - return; - } - setImportance(packageName, uid, - enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE); - mNotificationChannelLogger.logAppNotificationsAllowed(uid, packageName, enabled); - } - - /** * Sets whether any notifications from the app, represented by the given {@code pkgName} and * {@code uid}, have their importance locked by the user. Locked notifications don't get * considered for sentiment adjustments (and thus never show a blocking helper). @@ -2055,23 +1929,15 @@ public class PreferencesHelper implements RankingConfig { pw.print(" ("); pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); pw.print(')'); - if (!mPermissionHelper.isMigrationEnabled()) { - if (r.importance != DEFAULT_IMPORTANCE) { - pw.print(" importance="); - pw.print(NotificationListenerService.Ranking.importanceToString( - r.importance)); - } - } else { - Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); - if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { - pw.print(" importance="); - pw.print(NotificationListenerService.Ranking.importanceToString( - packagePermissions.get(key).first - ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); - pw.print(" userSet="); - pw.print(packagePermissions.get(key).second); - pkgsWithPermissionsToHandle.remove(key); - } + Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); + if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { + pw.print(" importance="); + pw.print(NotificationListenerService.Ranking.importanceToString( + packagePermissions.get(key).first + ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); + pw.print(" userSet="); + pw.print(packagePermissions.get(key).second); + pkgsWithPermissionsToHandle.remove(key); } if (r.priority != DEFAULT_PRIORITY) { pw.print(" priority="); @@ -2089,13 +1955,9 @@ public class PreferencesHelper implements RankingConfig { pw.print(" defaultAppLocked="); pw.print(r.defaultAppLockedImportance); } - if (r.oemLockedImportance != DEFAULT_OEM_LOCKED_IMPORTANCE) { - pw.print(" oemLocked="); - pw.print(r.oemLockedImportance); - } - if (!r.oemLockedChannels.isEmpty()) { - pw.print(" futureLockedChannels="); - pw.print(r.oemLockedChannels); + if (r.fixedImportance != DEFAULT_APP_LOCKED_IMPORTANCE) { + pw.print(" fixedImportance="); + pw.print(r.fixedImportance); } pw.println(); for (NotificationChannel channel : r.channels.values()) { @@ -2111,7 +1973,7 @@ public class PreferencesHelper implements RankingConfig { } } // Handle any remaining packages with permissions - if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { + if (pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { // p.first is the uid of this package; p.second is the package name if (filter.matches(p.second)) { @@ -2151,16 +2013,12 @@ public class PreferencesHelper implements RankingConfig { proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg); proto.write(RankingHelperProto.RecordProto.UID, r.uid); - if (mPermissionHelper.isMigrationEnabled()) { - Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); - if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { - proto.write(RankingHelperProto.RecordProto.IMPORTANCE, - packagePermissions.get(key).first - ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); - pkgsWithPermissionsToHandle.remove(key); - } - } else { - proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance); + Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); + if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) { + proto.write(RankingHelperProto.RecordProto.IMPORTANCE, + packagePermissions.get(key).first + ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); + pkgsWithPermissionsToHandle.remove(key); } proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority); proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility); @@ -2177,7 +2035,7 @@ public class PreferencesHelper implements RankingConfig { } } - if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { + if (pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (filter.matches(p.second)) { fToken = proto.start(fieldId); @@ -2217,25 +2075,22 @@ public class PreferencesHelper implements RankingConfig { // collect whether this package's importance info was user-set for later, if needed // before the migration is enabled, this will simply default to false in all cases. boolean importanceIsUserSet = false; - if (mPermissionHelper.isMigrationEnabled()) { - // Even if this package's data is not present, we need to write something; - // so default to IMPORTANCE_NONE, since if PM doesn't know about the package - // for some reason, notifications are not allowed. - int importance = IMPORTANCE_NONE; - Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); - if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) { - Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key); - importance = permissionPair.first - ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE; - // cache the second value for writing later - importanceIsUserSet = permissionPair.second; - - pkgsWithPermissionsToHandle.remove(key); - } - event.writeInt(importance); - } else { - event.writeInt(r.importance); - } + // Even if this package's data is not present, we need to write something; + // so default to IMPORTANCE_NONE, since if PM doesn't know about the package + // for some reason, notifications are not allowed. + int importance = IMPORTANCE_NONE; + Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); + if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) { + Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key); + importance = permissionPair.first + ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE; + // cache the second value for writing later + importanceIsUserSet = permissionPair.second; + + pkgsWithPermissionsToHandle.remove(key); + } + event.writeInt(importance); + event.writeInt(r.visibility); event.writeInt(r.lockedAppFields); event.writeBoolean(importanceIsUserSet); // optional bool user_set_importance = 5; @@ -2244,7 +2099,7 @@ public class PreferencesHelper implements RankingConfig { } // handle remaining packages with PackageManager permissions but not local settings - if (mPermissionHelper.isMigrationEnabled() && pkgPermissions != null) { + if (pkgPermissions != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; @@ -2357,22 +2212,14 @@ public class PreferencesHelper implements RankingConfig { try { PackagePreferences.put("userId", UserHandle.getUserId(r.uid)); PackagePreferences.put("packageName", r.pkg); - if (mPermissionHelper.isMigrationEnabled()) { - Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); - if (pkgPermissions != null - && pkgsWithPermissionsToHandle.contains(key)) { - PackagePreferences.put("importance", - NotificationListenerService.Ranking.importanceToString( - pkgPermissions.get(key).first - ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); - pkgsWithPermissionsToHandle.remove(key); - } - } else { - if (r.importance != DEFAULT_IMPORTANCE) { - PackagePreferences.put("importance", - NotificationListenerService.Ranking.importanceToString( - r.importance)); - } + Pair<Integer, String> key = new Pair<>(r.uid, r.pkg); + if (pkgPermissions != null + && pkgsWithPermissionsToHandle.contains(key)) { + PackagePreferences.put("importance", + NotificationListenerService.Ranking.importanceToString( + pkgPermissions.get(key).first + ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE)); + pkgsWithPermissionsToHandle.remove(key); } if (r.priority != DEFAULT_PRIORITY) { PackagePreferences.put("priority", @@ -2404,7 +2251,7 @@ public class PreferencesHelper implements RankingConfig { } // handle packages for which there are permissions but no local settings - if (mPermissionHelper.isMigrationEnabled() && pkgsWithPermissionsToHandle != null) { + if (pkgsWithPermissionsToHandle != null) { for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) { if (filter == null || filter.matches(p.second)) { JSONObject PackagePreferences = new JSONObject(); @@ -2443,8 +2290,7 @@ public class PreferencesHelper implements RankingConfig { public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { JSONArray bans = new JSONArray(); - Map<Integer, String> packageBans = mPermissionHelper.isMigrationEnabled() - ? getPermissionBasedPackageBans(pkgPermissions) : getPackageBans(); + Map<Integer, String> packageBans = getPermissionBasedPackageBans(pkgPermissions); for (Map.Entry<Integer, String> ban : packageBans.entrySet()) { final int userId = UserHandle.getUserId(ban.getKey()); final String packageName = ban.getValue(); @@ -2597,7 +2443,7 @@ public class PreferencesHelper implements RankingConfig { synchronized (mPackagePreferences) { mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r); } - if (mPermissionHelper.isMigrationEnabled() && r.migrateToPm) { + if (r.migrateToPm) { try { PackagePermission p = new PackagePermission( r.pkg, UserHandle.getUserId(r.uid), @@ -2853,9 +2699,8 @@ public class PreferencesHelper implements RankingConfig { int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; // these fields are loaded on boot from a different source of truth and so are not // written to notification policy xml - boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE; - List<String> oemLockedChannels = new ArrayList<>(); boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE; + boolean fixedImportance = DEFAULT_APP_LOCKED_IMPORTANCE; boolean hasSentInvalidMessage = false; boolean hasSentValidMessage = false; diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 398259333e16..3e9d90c440b6 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -24,8 +24,6 @@ import java.util.Collection; public interface RankingConfig { - void setImportance(String packageName, int uid, int importance); - int getImportance(String packageName, int uid); void setShowBadge(String packageName, int uid, boolean showBadge); boolean canShowBadge(String packageName, int uid); boolean badgingEnabled(UserHandle userHandle); diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index d26a1ac4fba0..9ea0192eaab5 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -91,17 +91,21 @@ public final class BackgroundDexOptService { // Possible return codes of individual optimization steps. /** Ok status: Optimizations finished, All packages were processed, can continue */ - private static final int STATUS_OK = 0; + /* package */ static final int STATUS_OK = 0; /** Optimizations should be aborted. Job scheduler requested it. */ - private static final int STATUS_ABORT_BY_CANCELLATION = 1; + /* package */ static final int STATUS_ABORT_BY_CANCELLATION = 1; /** Optimizations should be aborted. No space left on device. */ - private static final int STATUS_ABORT_NO_SPACE_LEFT = 2; + /* package */ static final int STATUS_ABORT_NO_SPACE_LEFT = 2; /** Optimizations should be aborted. Thermal throttling level too high. */ - private static final int STATUS_ABORT_THERMAL = 3; + /* package */ static final int STATUS_ABORT_THERMAL = 3; /** Battery level too low */ - private static final int STATUS_ABORT_BATTERY = 4; - /** {@link PackageDexOptimizer#DEX_OPT_FAILED} case */ - private static final int STATUS_DEX_OPT_FAILED = 5; + /* package */ static final int STATUS_ABORT_BATTERY = 4; + /** + * {@link PackageDexOptimizer#DEX_OPT_FAILED} case. This state means some packages have failed + * compilation during the job. Note that the failure will not be permanent as the next dexopt + * job will exclude those failed packages. + */ + /* package */ static final int STATUS_DEX_OPT_FAILED = 5; @IntDef(prefix = {"STATUS_"}, value = { STATUS_OK, @@ -525,7 +529,10 @@ public final class BackgroundDexOptService { } } - /** Returns true if completed */ + /** + * Returns whether we've successfully run the job. Note that it will return true even if some + * packages may have failed compiling. + */ private boolean runIdleOptimization(PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate) { synchronized (mLock) { @@ -541,7 +548,7 @@ public final class BackgroundDexOptService { mLastExecutionDurationMs = SystemClock.elapsedRealtime() - mLastExecutionStartTimeMs; } - return status == STATUS_OK; + return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED; } /** Gets the size of the directory. It uses recursion to go over all files. */ @@ -661,6 +668,11 @@ public final class BackgroundDexOptService { ArraySet<String> updatedPackages, boolean isPostBootUpdate) { boolean supportSecondaryDex = mInjector.supportSecondaryDex(); + // Keep the error if there is any error from any package. + @Status int status = STATUS_OK; + + // Other than cancellation, all packages will be processed even if an error happens + // in a package. for (String pkg : pkgs) { int abortCode = abortIdleOptimizations(lowStorageThreshold); if (abortCode != STATUS_OK) { @@ -670,10 +682,13 @@ public final class BackgroundDexOptService { @DexOptResult int primaryResult = optimizePackage(pkg, true /* isForPrimaryDex */, isPostBootUpdate); + if (primaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) { + return STATUS_ABORT_BY_CANCELLATION; + } if (primaryResult == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); - } else if (primaryResult != PackageDexOptimizer.DEX_OPT_SKIPPED) { - return convertPackageDexOptimizerStatusToInternal(primaryResult); + } else if (primaryResult == PackageDexOptimizer.DEX_OPT_FAILED) { + status = convertPackageDexOptimizerStatusToInternal(primaryResult); } if (!supportSecondaryDex) { @@ -682,12 +697,14 @@ public final class BackgroundDexOptService { @DexOptResult int secondaryResult = optimizePackage(pkg, false /* isForPrimaryDex */, isPostBootUpdate); - if (secondaryResult != PackageDexOptimizer.DEX_OPT_PERFORMED - && secondaryResult != PackageDexOptimizer.DEX_OPT_SKIPPED) { - return convertPackageDexOptimizerStatusToInternal(secondaryResult); + if (secondaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) { + return STATUS_ABORT_BY_CANCELLATION; + } + if (secondaryResult == PackageDexOptimizer.DEX_OPT_FAILED) { + status = convertPackageDexOptimizerStatusToInternal(secondaryResult); } } - return STATUS_OK; + return status; } /** @@ -779,9 +796,9 @@ public final class BackgroundDexOptService { @DexOptResult private int performDexOptPrimary(String pkg, int reason, int dexoptFlags) { + DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, dexoptFlags); return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true, - () -> mDexOptHelper.performDexOptWithStatus( - new DexoptOptions(pkg, reason, dexoptFlags))); + () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions)); } @DexOptResult @@ -791,7 +808,7 @@ public final class BackgroundDexOptService { dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false, () -> mDexOptHelper.performDexOpt(dexoptOptions) - ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED + ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED ); } diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index ed71f1eb5313..9d1f0704a3cf 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -197,7 +197,7 @@ public final class BroadcastHelper { final BroadcastOptions bOptions = getTemporaryAppAllowlistBroadcastOptions( REASON_LOCKED_BOOT_COMPLETED); am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null, - requiredPermissions, null, android.app.AppOpsManager.OP_NONE, + requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(), false, false, userId); // Deliver BOOT_COMPLETED only if user is unlocked @@ -207,7 +207,7 @@ public final class BroadcastHelper { bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); } am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null, - requiredPermissions, null, android.app.AppOpsManager.OP_NONE, + requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(), false, false, userId); } } catch (RemoteException e) { @@ -263,7 +263,7 @@ public final class BroadcastHelper { }; try { am.broadcastIntentWithFeature(null, null, intent, null, null, 0, null, null, - requiredPermissions, null, android.app.AppOpsManager.OP_NONE, null, false, + requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, null, false, false, UserHandle.USER_ALL); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -301,7 +301,7 @@ public final class BroadcastHelper { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); try { am.broadcastIntentWithFeature(null, null, intent, null, null, - 0, null, null, null, null, android.app.AppOpsManager.OP_NONE, + 0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); } catch (RemoteException e) { } diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index f1296e0cd7b0..8c33dd935822 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -29,9 +29,7 @@ import static com.android.server.pm.PackageManagerService.TAG; import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter; import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG; -import android.Manifest; import android.annotation.NonNull; -import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.AppGlobals; import android.content.Intent; @@ -44,7 +42,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.Log; import android.util.Slog; @@ -253,60 +250,10 @@ final class DexOptHelper { numberOfPackagesFailed}; } - /** - * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and - * compiles it if needed. - */ - private void checkAndDexOptSystemUi() { - Computer snapshot = mPm.snapshotComputer(); - String sysUiPackageName = - mPm.mContext.getString(com.android.internal.R.string.config_systemUi); - AndroidPackage pkg = snapshot.getPackage(sysUiPackageName); - if (pkg == null) { - Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting"); - return; - } - - boolean useProfileForDexopt = false; - File profileFile = new File(getPrebuildProfilePath(pkg)); - // Copy the profile to the reference profile path if it exists. Installd can only use a - // profile at the reference profile path for dexopt. - if (profileFile.exists()) { - try { - synchronized (mPm.mInstallLock) { - if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(), - pkg.getUid(), pkg.getPackageName(), - ArtManager.getProfileName(null))) { - useProfileForDexopt = true; - } else { - Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath()); - } - } - } catch (Exception e) { - Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e); - } - } - - // It could also be after mainline update, but we're not introducing a new reason just for - // this special case. - performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA, - useProfileForDexopt ? "speed-profile" : "speed", null /* splitName */, - 0 /* dexoptFlags */)); - } - - @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public void performPackageDexOptUpgradeIfNeeded() { PackageManagerServiceUtils.enforceSystemOrRoot( "Only the system can request package update"); - // The default is "true". - if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) { - // System UI is important to user experience, so we check it on every boot. It may need - // to be re-compiled after a mainline update or an OTA. - // TODO(b/227310505): Only do this after a mainline update or an OTA. - checkAndDexOptSystemUi(); - } - // We need to re-extract after an OTA. boolean causeUpgrade = mPm.isDeviceUpgrading(); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 32f0f109821d..9de667fbc6f5 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -44,6 +44,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class Installer extends SystemService { private static final String TAG = "Installer"; @@ -118,9 +121,13 @@ public class Installer extends SystemService { public static final int FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES = IInstalld.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES; + private static final long CONNECT_RETRY_DELAY_MS = DateUtils.SECOND_IN_MILLIS; + private static final long CONNECT_WAIT_MS = 10 * DateUtils.SECOND_IN_MILLIS; + private final boolean mIsolated; private volatile boolean mDeferSetFirstBoot; - private volatile IInstalld mInstalld; + private volatile IInstalld mInstalld = null; + private volatile CompletableFuture<IInstalld> mInstalldFuture = new CompletableFuture<>(); private volatile Object mWarnIfHeld; public Installer(Context context) { @@ -149,6 +156,7 @@ public class Installer extends SystemService { public void onStart() { if (mIsolated) { mInstalld = null; + mInstalldFuture = null; } else { connect(); } @@ -168,7 +176,9 @@ public class Installer extends SystemService { } if (binder != null) { - mInstalld = IInstalld.Stub.asInterface(binder); + IInstalld installd = IInstalld.Stub.asInterface(binder); + mInstalld = installd; + mInstalldFuture.complete(installd); try { invalidateMounts(); executeDeferredActions(); @@ -202,9 +212,18 @@ public class Installer extends SystemService { if (mIsolated) { Slog.i(TAG, "Ignoring request because this installer is isolated"); return false; - } else { - return true; } + + if (mInstalld == null && mInstalldFuture != null) { + try { + Slog.i(TAG, "installd not ready, waiting for: " + CONNECT_WAIT_MS + "ms"); + mInstalld = mInstalldFuture.get(CONNECT_WAIT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Ignoring request because this installer is not initialized", e); + } + } + + return mInstalld != null; } // We explicitly do NOT set previousAppId because the default value should always be 0. diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index ec6443db9f0f..652847ad1647 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -315,9 +315,9 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Deprecated public final List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, - int filterCallingUid, int userId) { - return getResolveIntentHelper().queryIntentReceiversInternal( - snapshot(), intent, resolvedType, flags, userId, filterCallingUid); + int filterCallingUid, int userId, boolean forSend) { + return getResolveIntentHelper().queryIntentReceiversInternal(snapshot(), intent, + resolvedType, flags, userId, filterCallingUid, forSend); } @Override @@ -352,10 +352,9 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override @Deprecated - public final void setDeviceOwnerProtectedPackages( - String deviceOwnerPackageName, List<String> packageNames) { - getProtectedPackages().setDeviceOwnerProtectedPackages( - deviceOwnerPackageName, packageNames); + public final void setOwnerProtectedPackages( + @UserIdInt int userId, @NonNull List<String> packageNames) { + getProtectedPackages().setOwnerProtectedPackages(userId, packageNames); } @Override diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java index bf4612973023..e9239889973a 100644 --- a/services/core/java/com/android/server/pm/ProtectedPackages.java +++ b/services/core/java/com/android/server/pm/ProtectedPackages.java @@ -16,11 +16,11 @@ package com.android.server.pm; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.os.UserHandle; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -56,7 +56,7 @@ public class ProtectedPackages { @Nullable @GuardedBy("this") - private final ArrayMap<String, Set<String>> mDeviceOwnerProtectedPackages = new ArrayMap<>(); + private final SparseArray<Set<String>> mOwnerProtectedPackages = new SparseArray<>(); private final Context mContext; @@ -79,13 +79,13 @@ public class ProtectedPackages { : profileOwnerPackages.clone(); } - /** Sets the protected packages for the device owner. */ - public synchronized void setDeviceOwnerProtectedPackages( - String deviceOwnerPackageName, List<String> packageNames) { + /** Sets packages protected by a device or profile owner. */ + public synchronized void setOwnerProtectedPackages( + @UserIdInt int userId, @NonNull List<String> packageNames) { if (packageNames.isEmpty()) { - mDeviceOwnerProtectedPackages.remove(deviceOwnerPackageName); + mOwnerProtectedPackages.remove(userId); } else { - mDeviceOwnerProtectedPackages.put(deviceOwnerPackageName, new ArraySet<>(packageNames)); + mOwnerProtectedPackages.put(userId, new ArraySet<>(packageNames)); } } @@ -123,19 +123,24 @@ public class ProtectedPackages { * <p>A protected package means that, apart from the package owner, no system or privileged apps * can modify its data or package state. */ - private synchronized boolean isProtectedPackage(String packageName) { + private synchronized boolean isProtectedPackage(@UserIdInt int userId, String packageName) { return packageName != null && (packageName.equals(mDeviceProvisioningPackage) - || isDeviceOwnerProtectedPackage(packageName)); + || isOwnerProtectedPackage(userId, packageName)); } - /** Returns {@code true} if the given package is a protected package set by any device owner. */ - private synchronized boolean isDeviceOwnerProtectedPackage(String packageName) { - for (Set<String> protectedPackages : mDeviceOwnerProtectedPackages.values()) { - if (protectedPackages.contains(packageName)) { - return true; - } - } - return false; + /** + * Returns {@code true} if the given package is a protected package set by any device or + * profile owner. + */ + private synchronized boolean isOwnerProtectedPackage( + @UserIdInt int userId, String packageName) { + return isPackageProtectedForUser(UserHandle.USER_ALL, packageName) + || isPackageProtectedForUser(userId, packageName); + } + + private synchronized boolean isPackageProtectedForUser(int userId, String packageName) { + int userIdx = mOwnerProtectedPackages.indexOfKey(userId); + return userIdx >= 0 && mOwnerProtectedPackages.valueAt(userIdx).contains(packageName); } /** @@ -146,7 +151,7 @@ public class ProtectedPackages { */ public boolean isPackageStateProtected(@UserIdInt int userId, String packageName) { return hasDeviceOwnerOrProfileOwner(userId, packageName) - || isProtectedPackage(packageName); + || isProtectedPackage(userId, packageName); } /** @@ -155,6 +160,6 @@ public class ProtectedPackages { */ public boolean isPackageDataProtected(@UserIdInt int userId, String packageName) { return hasDeviceOwnerOrProfileOwner(userId, packageName) - || isProtectedPackage(packageName); + || isProtectedPackage(userId, packageName); } } diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index b74670b77a11..2db1c2029d9c 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -306,16 +306,32 @@ final class ResolveIntentHelper { return new IntentSender(target); } - // In this method, we have to know the actual calling UID, but in some cases Binder's - // call identity is removed, so the UID has to be passed in explicitly. - public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent, + /** + * Retrieve all receivers that can handle a broadcast of the given intent. + * @param queryingUid the results will be filtered in the context of this UID instead. + */ + @NonNull + public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent, + String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId, + int queryingUid) { + return queryIntentReceiversInternal(computer, intent, resolvedType, flags, userId, + queryingUid, false); + } + + /** + * @see PackageManagerInternal#queryIntentReceivers(Intent, String, long, int, int, boolean) + */ + @NonNull + public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId, - int filterCallingUid) { + int filterCallingUid, boolean forSend) { if (!mUserManager.exists(userId)) return Collections.emptyList(); - computer.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/, - false /*checkShell*/, "query intent receivers"); - final String instantAppPkgName = computer.getInstantAppPackageName(filterCallingUid); - flags = computer.updateFlagsForResolve(flags, userId, filterCallingUid, + // The identity used to filter the receiver components + final int queryingUid = forSend ? Process.SYSTEM_UID : filterCallingUid; + computer.enforceCrossUserPermission(queryingUid, userId, + false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers"); + final String instantAppPkgName = computer.getInstantAppPackageName(queryingUid); + flags = computer.updateFlagsForResolve(flags, userId, queryingUid, false /*includeInstantApps*/, computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId, resolvedType, flags)); @@ -397,7 +413,7 @@ final class ResolveIntentHelper { list, true, originalIntent, resolvedType, filterCallingUid); } - return computer.applyPostResolutionFilter(list, instantAppPkgName, false, filterCallingUid, + return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid, false, userId, intent); } diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 95482d7c7f1a..974a1e1cd59d 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -70,9 +70,16 @@ class UserDataPreparer { void prepareUserData(int userId, int userSerial, int flags) { synchronized (mInstallLock) { final StorageManager storage = mContext.getSystemService(StorageManager.class); + /* + * Internal storage must be prepared before adoptable storage, since the user's volume + * keys are stored in their internal storage. + */ + prepareUserDataLI(null /* internal storage */, userId, userSerial, flags, true); for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { final String volumeUuid = vol.getFsUuid(); - prepareUserDataLI(volumeUuid, userId, userSerial, flags, true); + if (volumeUuid != null) { + prepareUserDataLI(volumeUuid, userId, userSerial, flags, true); + } } } } @@ -136,10 +143,17 @@ class UserDataPreparer { void destroyUserData(int userId, int flags) { synchronized (mInstallLock) { final StorageManager storage = mContext.getSystemService(StorageManager.class); + /* + * Volume destruction order isn't really important, but to avoid any weird issues we + * process internal storage last, the opposite of prepareUserData. + */ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { final String volumeUuid = vol.getFsUuid(); - destroyUserDataLI(volumeUuid, userId, flags); + if (volumeUuid != null) { + destroyUserDataLI(volumeUuid, userId, flags); + } } + destroyUserDataLI(null /* internal storage */, userId, flags); } } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index a04f6d64ef8f..f727c11879b0 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1018,8 +1018,6 @@ final class DefaultPermissionGrantPolicy { for (String packageName : packageNames) { grantPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId, PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS); - grantPermissionsToPackage(NO_PM_CACHE, packageName, userId, false, false, - NOTIFICATION_PERMISSIONS); } } diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java index 446e20b70279..6c2354f1b3e6 100644 --- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java +++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerInternal.java @@ -17,6 +17,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; /** @@ -105,6 +106,20 @@ public interface LegacyPermissionManagerInternal { void scheduleReadDefaultPermissionExceptions(); /** + * Check whether a particular package should have access to the microphone data from the + * SoundTrigger. + * + * @param uid the uid of the package you are checking against + * @param packageName the name of the package you are checking against + * @param attributionTag the attributionTag to attach to the app op transaction + * @param reason the reason to attach to the app op transaction + * @return {@code PERMISSION_GRANTED} if the permission is granted, + * or {@code PERMISSION_SOFT/HARD DENIED otherwise + */ + int checkSoundTriggerRecordAudioPermissionForDataDelivery(int uid, + @NonNull String packageName, @Nullable String attributionTag, @NonNull String reason); + + /** * Provider for package names. */ interface PackagesProvider { diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java index 360a04f7e9bc..88b4a94f7027 100644 --- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java @@ -16,12 +16,15 @@ package com.android.server.pm.permission; +import static android.Manifest.permission.RECORD_AUDIO; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -399,6 +402,21 @@ public class LegacyPermissionManagerService extends ILegacyPermissionManager.Stu public void scheduleReadDefaultPermissionExceptions() { mDefaultPermissionGrantPolicy.scheduleReadDefaultPermissionExceptions(); } + + @Override + public int checkSoundTriggerRecordAudioPermissionForDataDelivery(int uid, + @NonNull String packageName, @Nullable String attributionTag, + @NonNull String reason) { + int result = PermissionChecker.checkPermissionForPreflight(mContext, RECORD_AUDIO, -1, + uid, packageName); + if (result != PermissionChecker.PERMISSION_GRANTED) { + return result; + } + mContext.getSystemService(AppOpsManager.class).noteOpNoThrow( + AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, uid, packageName, + attributionTag, reason); + return result; + } } /** diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 7be83b03243a..d34682df3413 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE; import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED; @@ -97,6 +98,7 @@ import android.os.storage.StorageManager; import android.permission.IOnPermissionsChangeListener; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -333,7 +335,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt mPackageManagerInt.writeSettings(true); } @Override - public void onPermissionRevoked(int uid, int userId, String reason, boolean overrideKill) { + public void onPermissionRevoked(int uid, int userId, String reason, boolean overrideKill, + @Nullable String permissionName) { mOnPermissionChangeListeners.onPermissionsChanged(uid); // Critical; after this call the application should never have the permission @@ -342,13 +345,41 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt return; } - final int appId = UserHandle.getAppId(uid); - if (reason == null) { - mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED)); - } else { - mHandler.post(() -> killUid(appId, userId, reason)); + mHandler.post(() -> { + if (POST_NOTIFICATIONS.equals(permissionName) + && isAppBackupAndRestoreRunning(uid)) { + return; + } + + final int appId = UserHandle.getAppId(uid); + if (reason == null) { + killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); + } else { + killUid(appId, userId, reason); + } + }); + } + + private boolean isAppBackupAndRestoreRunning(int uid) { + if (checkUidPermission(uid, Manifest.permission.BACKUP) != PERMISSION_GRANTED) { + return false; + } + + try { + int userId = UserHandle.getUserId(uid); + boolean isInSetup = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, userId) == 0; + boolean isInDeferredSetup = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId) + == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED; + return isInSetup || isInDeferredSetup; + } catch (Settings.SettingNotFoundException e) { + Slog.w(LOG_TAG, "Failed to check if the user is in restore: " + e); + return false; } } + @Override public void onInstallPermissionRevoked() { mPackageManagerInt.writeSettings(true); @@ -1600,7 +1631,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt if (callback != null) { if (isRuntimePermission) { callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId, - reason, overrideKill); + reason, overrideKill, permName); } else { mDefaultPermissionCallback.onInstallPermissionRevoked(); } @@ -5246,7 +5277,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt onPermissionRevoked(uid, userId, reason, false); } public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason, - boolean overrideKill) {} + boolean overrideKill) { + onPermissionRevoked(uid, userId, reason, false, null); + } + public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason, + boolean overrideKill, @Nullable String permissionName) {} public void onInstallPermissionRevoked() {} public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {} public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index e1ff9ead6740..6ee9c66e328a 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -392,6 +392,9 @@ public class ParsingPackageUtils { if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) { liteParseFlags = flags; } + if ((flags & PARSE_APK_IN_APEX) != 0) { + liteParseFlags |= PARSE_APK_IN_APEX; + } final ParseResult<PackageLite> liteResult = ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits, liteParseFlags); diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index e157a277366f..a6d148c824c9 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -87,8 +87,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; /** - * Whether this device allows only the HotwordDetectionService to use OP_RECORD_AUDIO_HOTWORD - * which doesn't incur the privacy indicator. + * Whether this device allows only the HotwordDetectionService to use + * OP_RECORD_AUDIO_HOTWORD which doesn't incur the privacy indicator. */ private final boolean mIsHotwordDetectionServiceRequired; @@ -428,8 +428,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat if (!mIsHotwordDetectionServiceRequired) { return code; } - // Only the HotwordDetectionService can use the HOTWORD op which doesn't incur the - // privacy indicator. Downgrade to standard RECORD_AUDIO for other processes. + // Only the HotwordDetectionService can use the RECORD_AUDIO_HOTWORD op which doesn't + // incur the privacy indicator. Downgrade to standard RECORD_AUDIO for other processes. final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); if (hotwordDetectionServiceIdentity != null diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 7ba1cadc5c8b..977f79f6175d 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -1453,16 +1453,6 @@ public final class PermissionPolicyService extends SystemService { } } - try { - if (Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, UserHandle.USER_SYSTEM) - == 0) { - return false; - } - } catch (Settings.SettingNotFoundException e) { - return false; - } - if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS) || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user) || mKeyguardManager.isKeyguardLocked()) { diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java index 750d4004cb60..fd6ec065d421 100644 --- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java +++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java @@ -16,44 +16,105 @@ package com.android.server.sensorprivacy; +import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL; + +import android.annotation.ColorInt; import android.app.AppOpsManager; import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.hardware.lights.Light; import android.hardware.lights.LightState; import android.hardware.lights.LightsManager; import android.hardware.lights.LightsRequest; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.os.SystemClock; import android.permission.PermissionManager; import android.util.ArraySet; +import android.util.Pair; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.FgThread; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; + +class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener, + SensorEventListener { -class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener { + @VisibleForTesting + static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1); + private final Handler mHandler; + private final Executor mExecutor; private final Context mContext; + private final AppOpsManager mAppOpsManager; private final LightsManager mLightsManager; + private final SensorManager mSensorManager; private final Set<String> mActivePackages = new ArraySet<>(); private final Set<String> mActivePhonePackages = new ArraySet<>(); - private final int mCameraPrivacyLightColor; - private final List<Light> mCameraLights = new ArrayList<>(); - private final AppOpsManager mAppOpsManager; private LightsManager.LightsSession mLightsSession = null; + @ColorInt + private final int mDayColor; + @ColorInt + private final int mNightColor; + + private final Sensor mLightSensor; + + private boolean mIsAmbientLightListenerRegistered = false; + private final long mMovingAverageIntervalMillis; + /** When average of the time integral over the past {@link #mMovingAverageIntervalMillis} + * milliseconds of the log_1.1(lux(t)) is greater than this value, use the daytime brightness + * else use nighttime brightness. */ + private final long mNightThreshold; + private final ArrayDeque<Pair<Long, Integer>> mAmbientLightValues = new ArrayDeque<>(); + /** Tracks the Riemann sum of {@link #mAmbientLightValues} to avoid O(n) operations when sum is + * needed */ + private long mAlvSum = 0; + private int mLastLightColor = 0; + /** The elapsed real time that the ALS was started watching */ + private long mElapsedTimeStartedReading; + + private final Object mDelayedUpdateToken = new Object(); + + // Can't mock static native methods, workaround for testing + private long mElapsedRealTime = -1; + CameraPrivacyLightController(Context context) { + this(context, FgThread.get().getLooper()); + } + + @VisibleForTesting + CameraPrivacyLightController(Context context, Looper looper) { mContext = context; + mHandler = new Handler(looper); + mExecutor = new HandlerExecutor(mHandler); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mLightsManager = mContext.getSystemService(LightsManager.class); + mSensorManager = mContext.getSystemService(SensorManager.class); - mCameraPrivacyLightColor = mContext.getColor(R.color.camera_privacy_light); + mDayColor = mContext.getColor(R.color.camera_privacy_light_day); + mNightColor = mContext.getColor(R.color.camera_privacy_light_night); + mMovingAverageIntervalMillis = mContext.getResources() + .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis); + mNightThreshold = (long) (Math.log(mContext.getResources() + .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold)) + * LIGHT_VALUE_MULTIPLIER); List<Light> lights = mLightsManager.getLights(); for (int i = 0; i < lights.size(); i++) { @@ -64,12 +125,60 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis } if (mCameraLights.isEmpty()) { + mLightSensor = null; return; } mAppOpsManager.startWatchingActive( new String[] {AppOpsManager.OPSTR_CAMERA, AppOpsManager.OPSTR_PHONE_CALL_CAMERA}, - FgThread.getExecutor(), this); + mExecutor, this); + + // It may be useful in the future to configure devices to know which lights are near which + // sensors so that we can control individual lights based on their environment. + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + } + + private void addElement(long time, int value) { + if (mAmbientLightValues.isEmpty()) { + // Eliminate the size == 1 edge case and assume the light value has been constant for + // the previous interval + mAmbientLightValues.add(new Pair<>(time - getCurrentIntervalMillis() - 1, value)); + } + Pair<Long, Integer> lastElement = mAmbientLightValues.peekLast(); + mAmbientLightValues.add(new Pair<>(time, value)); + + mAlvSum += (time - lastElement.first) * lastElement.second; + removeObsoleteData(time); + } + + private void removeObsoleteData(long time) { + while (mAmbientLightValues.size() > 1) { + Pair<Long, Integer> element0 = mAmbientLightValues.pollFirst(); // NOTICE: POLL + Pair<Long, Integer> element1 = mAmbientLightValues.peekFirst(); // NOTICE: PEEK + if (element1.first > time - getCurrentIntervalMillis()) { + mAmbientLightValues.addFirst(element0); + break; + } + mAlvSum -= (element1.first - element0.first) * element0.second; + } + } + + /** + * Gives the Riemann sum of {@link #mAmbientLightValues} where the part of the interval that + * stretches outside the time window is removed and the time since the last change is added in. + */ + private long getLiveAmbientLightTotal() { + if (mAmbientLightValues.isEmpty()) { + return mAlvSum; + } + long time = getElapsedRealTime(); + removeObsoleteData(time); + + Pair<Long, Integer> firstElement = mAmbientLightValues.peekFirst(); + Pair<Long, Integer> lastElement = mAmbientLightValues.peekLast(); + + return mAlvSum - Math.max(0, time - getCurrentIntervalMillis() - firstElement.first) + * firstElement.second + (time - lastElement.first) * lastElement.second; } @Override @@ -93,10 +202,16 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis } private void updateLightSession() { + if (Looper.myLooper() != mHandler.getLooper()) { + mHandler.post(this::updateLightSession); + return; + } + Set<String> exemptedPackages = PermissionManager.getIndicatorExemptedPackages(mContext); boolean shouldSessionEnd = exemptedPackages.containsAll(mActivePackages) && exemptedPackages.containsAll(mActivePhonePackages); + updateSensorListener(shouldSessionEnd); if (shouldSessionEnd) { if (mLightsSession == null) { @@ -106,20 +221,73 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis mLightsSession.close(); mLightsSession = null; } else { - if (mLightsSession != null) { + int lightColor; + if (mLightSensor != null && getLiveAmbientLightTotal() + < getCurrentIntervalMillis() * mNightThreshold) { + lightColor = mNightColor; + } else { + lightColor = mDayColor; + } + + if (mLastLightColor == lightColor && mLightsSession != null) { return; } + mLastLightColor = lightColor; LightsRequest.Builder requestBuilder = new LightsRequest.Builder(); for (int i = 0; i < mCameraLights.size(); i++) { requestBuilder.addLight(mCameraLights.get(i), new LightState.Builder() - .setColor(mCameraPrivacyLightColor) + .setColor(lightColor) .build()); } - mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE); + if (mLightsSession == null) { + mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE); + } + mLightsSession.requestLights(requestBuilder.build()); } } + + private void updateSensorListener(boolean shouldSessionEnd) { + if (shouldSessionEnd && mIsAmbientLightListenerRegistered) { + mSensorManager.unregisterListener(this); + mIsAmbientLightListenerRegistered = false; + } + if (!shouldSessionEnd && !mIsAmbientLightListenerRegistered && mLightSensor != null) { + mSensorManager.registerListener(this, mLightSensor, SENSOR_DELAY_NORMAL, mHandler); + mIsAmbientLightListenerRegistered = true; + mElapsedTimeStartedReading = getElapsedRealTime(); + } + } + + private long getElapsedRealTime() { + return mElapsedRealTime == -1 ? SystemClock.elapsedRealtime() : mElapsedRealTime; + } + + @VisibleForTesting + void setElapsedRealTime(long time) { + mElapsedRealTime = time; + } + + @Override + public void onSensorChanged(SensorEvent event) { + // Using log space to represent human sensation (Fechner's Law) instead of lux + // because lux values causes bright flashes to skew the average very high. + addElement(event.timestamp, Math.max(0, + (int) (Math.log(event.values[0]) * LIGHT_VALUE_MULTIPLIER))); + updateLightSession(); + mHandler.removeCallbacksAndMessages(mDelayedUpdateToken); + mHandler.postDelayed(CameraPrivacyLightController.this::updateLightSession, + mDelayedUpdateToken, mMovingAverageIntervalMillis); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} + + private long getCurrentIntervalMillis() { + return Math.min(mMovingAverageIntervalMillis, + getElapsedRealTime() - mElapsedTimeStartedReading); + } } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 3c779f387673..c354f116af5f 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -25,6 +25,7 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; +import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; @@ -1067,6 +1068,10 @@ public final class SensorPrivacyService extends SystemService { mAppOpsRestrictionToken); mAppOpsManagerInternal.setGlobalRestriction(OP_PHONE_CALL_MICROPHONE, enabled, mAppOpsRestrictionToken); + // We don't show the dialog for RECEIVE_SOUNDTRIGGER_AUDIO, but still want to + // restrict it when the microphone is disabled + mAppOpsManagerInternal.setGlobalRestriction(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + enabled, mAppOpsRestrictionToken); break; case CAMERA: mAppOpsManagerInternal.setGlobalRestriction(OP_CAMERA, enabled, diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java index f3d151fe5cc4..13fe14caa1f9 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java @@ -41,6 +41,9 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; +import com.android.server.LocalServices; +import com.android.server.pm.permission.LegacyPermissionManagerInternal; + import java.io.PrintWriter; import java.util.Objects; @@ -120,8 +123,7 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware * Throws a {@link SecurityException} iff the originator has permission to receive data. */ void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) { - enforcePermissionForDataDelivery(mContext, identity, RECORD_AUDIO, - reason); + enforceSoundTriggerRecordAudioPermissionForDataDelivery(identity, reason); enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD, reason); } @@ -147,6 +149,19 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware } } + private static void enforceSoundTriggerRecordAudioPermissionForDataDelivery( + @NonNull Identity identity, @NonNull String reason) { + LegacyPermissionManagerInternal lpmi = + LocalServices.getService(LegacyPermissionManagerInternal.class); + final int status = lpmi.checkSoundTriggerRecordAudioPermissionForDataDelivery(identity.uid, + identity.packageName, identity.attributionTag, reason); + if (status != PermissionChecker.PERMISSION_GRANTED) { + throw new SecurityException( + String.format("Failed to obtain permission RECORD_AUDIO for identity %s", + ObjectPrinter.print(identity, 16))); + } + } + /** * Throws a {@link SecurityException} if originator permanently doesn't have the given * permission. diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 5e7b5860629d..977f6fd7b528 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -204,6 +204,7 @@ import com.android.server.SystemServiceManager; import com.android.server.am.MemoryStatUtil.MemoryStat; import com.android.server.health.HealthServiceWrapper; import com.android.server.notification.NotificationManagerService; +import com.android.server.pm.UserManagerInternal; import com.android.server.stats.pull.IonMemoryUtil.IonAllocations; import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; import com.android.server.stats.pull.netstats.NetworkStatsExt; @@ -4106,11 +4107,24 @@ public class StatsPullAtomService extends SystemService { // Incremental is not enabled on this device. The result list will be empty. return StatsManager.PULL_SUCCESS; } - List<PackageInfo> installedPackages = pm.getInstalledPackages(0); - for (PackageInfo pi : installedPackages) { - if (IncrementalManager.isIncrementalPath(pi.applicationInfo.getBaseCodePath())) { - pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid)); + final long token = Binder.clearCallingIdentity(); + try { + int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds(); + for (int userId : userIds) { + List<PackageInfo> installedPackages = pm.getInstalledPackagesAsUser(0, userId); + for (PackageInfo pi : installedPackages) { + if (IncrementalManager.isIncrementalPath( + pi.applicationInfo.getBaseCodePath())) { + pulledData.add( + FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid)); + } + } } + } catch (Exception e) { + Slog.e(TAG, "failed to pullInstalledIncrementalPackagesLocked", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); } return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index ee30fa2ac928..fb9a4d41ee90 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -15,6 +15,8 @@ */ package com.android.server.tv.tunerresourcemanager; +import android.media.tv.tunerresourcemanager.TunerResourceManager; + import java.util.HashSet; import java.util.Set; @@ -63,6 +65,11 @@ public final class ClientProfile { private int mNiceValue; /** + * The handle of the primary frontend resource + */ + private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; + + /** * List of the frontend handles that are used by the current client. */ private Set<Integer> mUsingFrontendHandles = new HashSet<>(); @@ -175,6 +182,22 @@ public final class ClientProfile { } /** + * Set the primary frontend used by the client + * + * @param frontendHandle being used. + */ + public void setPrimaryFrontend(int frontendHandle) { + mPrimaryUsingFrontendHandle = frontendHandle; + } + + /** + * Get the primary frontend used by the client + */ + public int getPrimaryFrontend() { + return mPrimaryUsingFrontendHandle; + } + + /** * Update the set of client that share frontend with the current client. * * @param clientId the client to share the fe with the current client. @@ -206,6 +229,7 @@ public final class ClientProfile { public void releaseFrontend() { mUsingFrontendHandles.clear(); mShareFeClientIds.clear(); + mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; } /** @@ -276,6 +300,7 @@ public final class ClientProfile { public void reclaimAllResources() { mUsingFrontendHandles.clear(); mShareFeClientIds.clear(); + mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; mUsingLnbHandles.clear(); mUsingCasSystemId = INVALID_RESOURCE_ID; mUsingCiCamId = INVALID_RESOURCE_ID; diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index af705d597af2..6162d716b85e 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -42,6 +42,7 @@ import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -72,14 +73,28 @@ public class TunerResourceManagerService extends SystemService implements IBinde private static final long INVALID_THREAD_ID = -1; private static final long TRMS_LOCK_TIMEOUT = 500; + private static final int INVALID_FE_COUNT = -1; + // Map of the registered client profiles private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>(); private int mNextUnusedClientId = 0; // Map of the current available frontend resources private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); - // Backup Map of the current available frontend resources + // SparseIntArray of the max usable number for each frontend resource type + private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray(); + // SparseIntArray of the currently used number for each frontend resource type + private SparseIntArray mFrontendUsedNums = new SparseIntArray(); + // SparseIntArray of the existing number for each frontend resource type + private SparseIntArray mFrontendExistingNums = new SparseIntArray(); + + // Backups for the frontend resource maps for enabling testing with custom resource maps + // such as TunerTest.testHasUnusedFrontend1() private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); + private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray(); + private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray(); + private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray(); + // Map of the current available lnb resources private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); // Map of the current available Cas resources @@ -268,6 +283,29 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override + public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) { + enforceTunerAccessPermission("requestFrontend"); + enforceTrmAccessPermission("requestFrontend"); + if (maxUsableNum < 0) { + Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum + + " frontendType:" + frontendType); + return false; + } + synchronized (mLock) { + return setMaxNumberOfFrontendsInternal(frontendType, maxUsableNum); + } + } + + @Override + public int getMaxNumberOfFrontends(int frontendType) { + enforceTunerAccessPermission("requestFrontend"); + enforceTrmAccessPermission("requestFrontend"); + synchronized (mLock) { + return getMaxNumberOfFrontendsInternal(frontendType); + } + } + + @Override public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException { enforceTunerAccessPermission("shareFrontend"); enforceTrmAccessPermission("shareFrontend"); @@ -572,71 +610,19 @@ public class TunerResourceManagerService extends SystemService implements IBinde } synchronized (mLock) { - if (mClientProfiles != null) { - pw.println("ClientProfiles:"); - pw.increaseIndent(); - for (Map.Entry<Integer, ClientProfile> entry : mClientProfiles.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } - - if (mFrontendResources != null) { - pw.println("FrontendResources:"); - pw.increaseIndent(); - for (Map.Entry<Integer, FrontendResource> entry - : mFrontendResources.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } - - if (mFrontendResourcesBackup != null) { - pw.println("FrontendResourcesBackUp:"); - pw.increaseIndent(); - for (Map.Entry<Integer, FrontendResource> entry - : mFrontendResourcesBackup.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } - - if (mLnbResources != null) { - pw.println("LnbResources:"); - pw.increaseIndent(); - for (Map.Entry<Integer, LnbResource> entry : mLnbResources.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } - - if (mCasResources != null) { - pw.println("CasResources:"); - pw.increaseIndent(); - for (Map.Entry<Integer, CasResource> entry : mCasResources.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } - - if (mCiCamResources != null) { - pw.println("CiCamResources:"); - pw.increaseIndent(); - for (Map.Entry<Integer, CiCamResource> entry : mCiCamResources.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } - - if (mListeners != null) { - pw.println("Listners:"); - pw.increaseIndent(); - for (Map.Entry<Integer, ResourcesReclaimListenerRecord> entry - : mListeners.entrySet()) { - pw.println(entry.getKey() + " : " + entry.getValue()); - } - pw.decreaseIndent(); - } + dumpMap(mClientProfiles, "ClientProfiles:", "\n", pw); + dumpMap(mFrontendResources, "FrontendResources:", "\n", pw); + dumpSIA(mFrontendExistingNums, "FrontendExistingNums:", ", ", pw); + dumpSIA(mFrontendUsedNums, "FrontendUsedNums:", ", ", pw); + dumpSIA(mFrontendMaxUsableNums, "FrontendMaxUsableNums:", ", ", pw); + dumpMap(mFrontendResourcesBackup, "FrontendResourcesBackUp:", "\n", pw); + dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw); + dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw); + dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw); + dumpMap(mLnbResources, "LnbResource:", "\n", pw); + dumpMap(mCasResources, "CasResource:", "\n", pw); + dumpMap(mCiCamResources, "CiCamResource:", "\n", pw); + dumpMap(mListeners, "Listners:", "\n", pw); } } @@ -786,10 +772,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void storeResourceMapInternal(int resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: - if (mFrontendResources != null && mFrontendResources.size() > 0) { - mFrontendResourcesBackup.putAll(mFrontendResources); - mFrontendResources.clear(); - } + replaceFeResourceMap(mFrontendResources, mFrontendResourcesBackup); + replaceFeCounts(mFrontendExistingNums, mFrontendExistingNumsBackup); + replaceFeCounts(mFrontendUsedNums, mFrontendUsedNumsBackup); + replaceFeCounts(mFrontendMaxUsableNums, mFrontendMaxUsableNumsBackup); break; // TODO: implement for other resource type when needed default: @@ -800,9 +786,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void clearResourceMapInternal(int resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: - if (mFrontendResources != null) { - mFrontendResources.clear(); - } + replaceFeResourceMap(null, mFrontendResources); + replaceFeCounts(null, mFrontendExistingNums); + replaceFeCounts(null, mFrontendUsedNums); + replaceFeCounts(null, mFrontendMaxUsableNums); break; // TODO: implement for other resource type when needed default: @@ -813,12 +800,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void restoreResourceMapInternal(int resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: - if (mFrontendResourcesBackup != null - && mFrontendResourcesBackup.size() > 0) { - mFrontendResources.clear(); - mFrontendResources.putAll(mFrontendResourcesBackup); - mFrontendResourcesBackup.clear(); - } + replaceFeResourceMap(mFrontendResourcesBackup, mFrontendResources); + replaceFeCounts(mFrontendExistingNumsBackup, mFrontendExistingNums); + replaceFeCounts(mFrontendUsedNumsBackup, mFrontendUsedNums); + replaceFeCounts(mFrontendMaxUsableNumsBackup, mFrontendMaxUsableNums); break; // TODO: implement for other resource type when needed default: @@ -954,6 +939,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde for (FrontendResource fr : getFrontendResources().values()) { if (fr.getType() == request.frontendType) { if (!fr.isInUse()) { + // Unused resource cannot be acquired if the max is already reached, but + // TRM still has to look for the reclaim candidate + if (isFrontendMaxNumUseReached(request.frontendType)) { + continue; + } // Grant unused frontend with no exclusive group members first. if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { grantingFrontendHandle = fr.getHandle(); @@ -1021,6 +1011,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { getFrontendResource(inUseHandle).setOwner(newOwnerId); } + // change the primary frontend + newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend()); + currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE); // double check there is no other resources tied to the previous owner for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); @@ -1657,11 +1650,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde FrontendResource grantingFrontend = getFrontendResource(grantingHandle); ClientProfile ownerProfile = getClientProfile(ownerClientId); grantingFrontend.setOwner(ownerClientId); + increFrontendNum(mFrontendUsedNums, grantingFrontend.getType()); ownerProfile.useFrontend(grantingHandle); for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); ownerProfile.useFrontend(exclusiveGroupMember); } + ownerProfile.setPrimaryFrontend(grantingHandle); } private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { @@ -1755,6 +1750,109 @@ public class TunerResourceManagerService extends SystemService implements IBinde return mFrontendResources; } + private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) { + int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT); + if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) { + mFrontendMaxUsableNums.put(frontendType, maxUsableNum); + return true; + } else { + Slog.e(TAG, "max number of frontend for frontendType: " + frontendType + + " cannot be set to a value lower than the current usage count." + + " (requested max num = " + maxUsableNum + ", current usage = " + usedNum); + return false; + } + } + + private int getMaxNumberOfFrontendsInternal(int frontendType) { + int existingNum = mFrontendExistingNums.get(frontendType, INVALID_FE_COUNT); + if (existingNum == INVALID_FE_COUNT) { + Log.e(TAG, "existingNum is -1 for " + frontendType); + return -1; + } + int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT); + if (maxUsableNum == INVALID_FE_COUNT) { + return existingNum; + } else { + return maxUsableNum; + } + } + + private boolean isFrontendMaxNumUseReached(int frontendType) { + int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT); + if (maxUsableNum == INVALID_FE_COUNT) { + return false; + } + int useNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT); + if (useNum == INVALID_FE_COUNT) { + useNum = 0; + } + return useNum >= maxUsableNum; + } + + private void increFrontendNum(SparseIntArray targetNums, int frontendType) { + int num = targetNums.get(frontendType, INVALID_FE_COUNT); + if (num == INVALID_FE_COUNT) { + targetNums.put(frontendType, 1); + } else { + targetNums.put(frontendType, num + 1); + } + } + + private void decreFrontendNum(SparseIntArray targetNums, int frontendType) { + int num = targetNums.get(frontendType, INVALID_FE_COUNT); + if (num != INVALID_FE_COUNT) { + targetNums.put(frontendType, num - 1); + } + } + + private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer, + FrontendResource> dstMap) { + if (dstMap != null) { + dstMap.clear(); + if (srcMap != null && srcMap.size() > 0) { + dstMap.putAll(srcMap); + } + } + } + + private void replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts) { + if (dstCounts != null) { + dstCounts.clear(); + if (srcCounts != null) { + for (int i = 0; i < srcCounts.size(); i++) { + dstCounts.put(srcCounts.keyAt(i), srcCounts.valueAt(i)); + } + } + } + } + private void dumpMap(Map<?, ?> targetMap, String headline, String delimiter, + IndentingPrintWriter pw) { + if (targetMap != null) { + pw.println(headline); + pw.increaseIndent(); + for (Map.Entry<?, ?> entry : targetMap.entrySet()) { + pw.print(entry.getKey() + " : " + entry.getValue()); + pw.print(delimiter); + } + pw.println(); + pw.decreaseIndent(); + } + } + + private void dumpSIA(SparseIntArray array, String headline, String delimiter, + IndentingPrintWriter pw) { + if (array != null) { + pw.println(headline); + pw.increaseIndent(); + for (int i = 0; i < array.size(); i++) { + pw.print(array.keyAt(i) + " : " + array.valueAt(i)); + pw.print(delimiter); + } + pw.println(); + pw.decreaseIndent(); + } + } + private void addFrontendResource(FrontendResource newFe) { // Update the exclusive group member list in all the existing Frontend resource for (FrontendResource fe : getFrontendResources().values()) { @@ -1771,6 +1869,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } // Update resource list and available id list mFrontendResources.put(newFe.getHandle(), newFe); + increFrontendNum(mFrontendExistingNums, newFe.getType()); + } private void removeFrontendResource(int removingHandle) { @@ -1789,6 +1889,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde getFrontendResource(excGroupmemberFeHandle) .removeExclusiveGroupMemberFeId(fe.getHandle()); } + decreFrontendNum(mFrontendExistingNums, fe.getType()); mFrontendResources.remove(removingHandle); } @@ -1918,6 +2019,15 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } + + int primaryFeId = profile.getPrimaryFrontend(); + if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) { + FrontendResource primaryFe = getFrontendResource(primaryFeId); + if (primaryFe != null) { + decreFrontendNum(mFrontendUsedNums, primaryFe.getType()); + } + } + profile.releaseFrontend(); } diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java index 75e39ebb32d8..07474a63cb2a 100644 --- a/services/core/java/com/android/server/utils/WatchedArrayList.java +++ b/services/core/java/com/android/server/utils/WatchedArrayList.java @@ -416,7 +416,7 @@ public class WatchedArrayList<E> extends WatchableImpl dst.mStorage.ensureCapacity(end); for (int i = 0; i < end; i++) { final E val = Snapshots.maybeSnapshot(src.get(i)); - dst.add(val); + dst.mStorage.add(val); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedArrayMap.java b/services/core/java/com/android/server/utils/WatchedArrayMap.java index 7c1cde8502bd..63441aac8e1f 100644 --- a/services/core/java/com/android/server/utils/WatchedArrayMap.java +++ b/services/core/java/com/android/server/utils/WatchedArrayMap.java @@ -465,7 +465,7 @@ public class WatchedArrayMap<K, V> extends WatchableImpl for (int i = 0; i < end; i++) { final V val = Snapshots.maybeSnapshot(src.valueAt(i)); final K key = src.keyAt(i); - dst.put(key, val); + dst.mStorage.put(key, val); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedArraySet.java b/services/core/java/com/android/server/utils/WatchedArraySet.java index ec80261a2196..a2eaed72dd65 100644 --- a/services/core/java/com/android/server/utils/WatchedArraySet.java +++ b/services/core/java/com/android/server/utils/WatchedArraySet.java @@ -427,7 +427,7 @@ public class WatchedArraySet<E> extends WatchableImpl dst.mStorage.ensureCapacity(end); for (int i = 0; i < end; i++) { final E val = Snapshots.maybeSnapshot(src.valueAt(i)); - dst.append(val); + dst.mStorage.append(val); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedLongSparseArray.java b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java index bf23de12ffef..9da9e75f98fb 100644 --- a/services/core/java/com/android/server/utils/WatchedLongSparseArray.java +++ b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java @@ -410,7 +410,7 @@ public class WatchedLongSparseArray<E> extends WatchableImpl for (int i = 0; i < end; i++) { final E val = Snapshots.maybeSnapshot(src.valueAt(i)); final long key = src.keyAt(i); - dst.put(key, val); + dst.mStorage.put(key, val); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedSparseArray.java b/services/core/java/com/android/server/utils/WatchedSparseArray.java index 9b99b9176d19..6ce38b71d9cd 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseArray.java @@ -490,7 +490,7 @@ public class WatchedSparseArray<E> extends WatchableImpl for (int i = 0; i < end; i++) { final E val = Snapshots.maybeSnapshot(src.valueAt(i)); final int key = src.keyAt(i); - dst.put(key, val); + dst.mStorage.put(key, val); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java index 772a8d07cffb..50e2272afb72 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanArray.java @@ -310,7 +310,7 @@ public class WatchedSparseBooleanArray extends WatchableImpl } final int end = src.size(); for (int i = 0; i < end; i++) { - dst.put(src.keyAt(i), src.valueAt(i)); + dst.mStorage.put(src.keyAt(i), src.valueAt(i)); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedSparseIntArray.java b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java index 72705bf24199..53d168245180 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseIntArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseIntArray.java @@ -315,7 +315,7 @@ public class WatchedSparseIntArray extends WatchableImpl } final int end = src.size(); for (int i = 0; i < end; i++) { - dst.put(src.keyAt(i), src.valueAt(i)); + dst.mStorage.put(src.keyAt(i), src.valueAt(i)); } dst.seal(); } diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java index 05db12e49a13..77750ed92273 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java @@ -169,7 +169,7 @@ public class WatchedSparseSetArray<T> extends WatchableImpl implements Snappable final ArraySet set = src.get(i); final int setSize = set.size(); for (int j = 0; j < setSize; j++) { - dst.add(src.keyAt(i), set.valueAt(j)); + dst.mStorage.add(src.keyAt(i), set.valueAt(j)); } } dst.seal(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 791d193f36ab..189fff865ea0 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3532,7 +3532,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final ActivityRecord next = getDisplayArea().topRunningActivity( true /* considerKeyguardState */); - // If the finishing activity is the last activity of a organized TaskFragment and has an + // If the finishing activity is the last activity of an organized TaskFragment and has an // adjacent TaskFragment, check if the activity removal should be delayed. boolean delayRemoval = false; final TaskFragment taskFragment = getTaskFragment(); @@ -3540,7 +3540,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final TaskFragment organized = taskFragment.getOrganizedTaskFragment(); final TaskFragment adjacent = organized != null ? organized.getAdjacentTaskFragment() : null; - if (adjacent != null && organized.topRunningActivity() == null) { + if (adjacent != null && next.isDescendantOf(adjacent) + && organized.topRunningActivity() == null) { delayRemoval = organized.isDelayLastActivityRemoval(); } } @@ -5094,13 +5095,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // still check DC#okToAnimate again if the transition animation is fine to apply. // TODO(new-app-transition): Rewrite this logic using WM Shell. final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS); + final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode + && mVisible == visible; if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn()) && (appTransition.isTransitionSet() || (recentsAnimating && !isActivityTypeHome())) - // If the visibility change during enter PIP, we don't want to include it in app - // transition to affect the animation theme, because the Pip organizer will animate - // the entering PIP instead. - && !mWaitForEnteringPinnedMode) { + // If the visibility is not changed during enter PIP, we don't want to include it in + // app transition to affect the animation theme, because the Pip organizer will + // animate the entering PIP instead. + && !isEnteringPipWithoutVisibleChange) { if (visible) { displayContent.mOpeningApps.add(this); mEnteringAnimation = true; @@ -6339,8 +6342,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null; if (associatedTask == null) { removeStartingWindow(); - } else if (associatedTask.getActivity( - r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) { + } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn + // Don't block starting window removal if an Activity can't be a starting window + // target. + && r.mSharedStartingData != null) == null) { // The last drawn activity may not be the one that owns the starting window. final ActivityRecord r = associatedTask.topActivityContainsStartingWindow(); if (r != null) { @@ -7983,20 +7988,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // orientation with insets applied. return; } - // Not using Task#isResizeable() or ActivityRecord#isResizeable() directly because app - // compatibility testing showed that android:supportsPictureInPicture="true" alone is not - // sufficient signal for not letterboxing an app. - // TODO(214602463): Remove multi-window check since orientation and aspect ratio - // restrictions should always be applied in multi-window. - final boolean isResizeable = task != null - // Activity should be resizable if the task is. - ? task.isResizeable(/* checkPictureInPictureSupport */ false) - || isResizeable(/* checkPictureInPictureSupport */ false) - : isResizeable(/* checkPictureInPictureSupport */ false); - if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) { - // Ignore orientation request for resizable apps in multi window. - return; - } + if (windowingMode == WINDOWING_MODE_PINNED) { // PiP bounds have higher priority than the requested orientation. Otherwise the // activity may be squeezed into a small piece. @@ -9338,6 +9330,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A sb.append(mUserId); sb.append(' '); sb.append(intent.getComponent().flattenToShortString()); + sb.append("}"); stringName = sb.toString(); return stringName; } diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 72408b67de41..d60981fcf504 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -518,6 +518,8 @@ public class ActivityStartController { .setRequestCode(-1) .setCallingUid(callingUid) .setCallingPid(callingPid) + .setRealCallingUid(callingUid) + .setRealCallingPid(callingPid) .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId()) .execute(); } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 701fc9441acb..cb6559715c55 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -68,6 +68,7 @@ import static com.android.server.wm.AppTransition.isNormalTransit; import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp; import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -1142,13 +1143,17 @@ public class AppTransitionController { if (activity == null) { continue; } + if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Delaying app transition for recents animation to finish"); + return false; + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Check opening app=%s: allDrawn=%b startingDisplayed=%b " + "startingMoved=%b isRelaunching()=%b startingWindow=%s", activity, activity.allDrawn, activity.startingDisplayed, activity.startingMoved, activity.isRelaunching(), activity.mStartingWindow); - final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { return false; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d0baa238f732..66eca990b14f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5265,7 +5265,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void prepareAppTransition(@WindowManager.TransitionType int transit, @WindowManager.TransitionFlags int flags) { final boolean prepared = mAppTransition.prepareAppTransition(transit, flags); - if (prepared && okToAnimate()) { + if (prepared && okToAnimate() && transit != TRANSIT_NONE) { mSkipAppTransitionAnimation = false; } } @@ -6228,6 +6228,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** * Sets if Display APIs should be sandboxed to the activity window bounds. */ + @VisibleForTesting void setSandboxDisplayApis(boolean sandboxDisplayApis) { mSandboxDisplayApis = sandboxDisplayApis; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 62998cb6bc40..d62bcdfd446b 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -651,17 +651,19 @@ public class DisplayPolicy { mForceShowNavBarSettingsObserver = new ForceShowNavBarSettingsObserver( mHandler, mContext); - mForceShowNavBarSettingsObserver.setOnChangeRunnable(() -> { - synchronized (mLock) { - mForceShowNavigationBarEnabled = - mForceShowNavBarSettingsObserver.isEnabled(); - updateSystemBarAttributes(); - } - }); + mForceShowNavBarSettingsObserver.setOnChangeRunnable(this::updateForceShowNavBarSettings); mForceShowNavigationBarEnabled = mForceShowNavBarSettingsObserver.isEnabled(); mHandler.post(mForceShowNavBarSettingsObserver::register); } + private void updateForceShowNavBarSettings() { + synchronized (mLock) { + mForceShowNavigationBarEnabled = + mForceShowNavBarSettingsObserver.isEnabled(); + updateSystemBarAttributes(); + } + } + /** * Returns the first non-null alt bar window matching the given position. */ @@ -1801,6 +1803,7 @@ public class DisplayPolicy { */ public void switchUser() { updateCurrentUserResources(); + updateForceShowNavBarSettings(); } /** diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index ea72e12783c3..ce416ad89c95 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -416,8 +416,22 @@ final class InputMonitor { final IBinder focusToken = focus != null ? focus.mInputChannelToken : null; if (focusToken == null) { + if (recentsAnimationInputConsumer != null + && recentsAnimationInputConsumer.mWindowHandle != null + && mInputFocus == recentsAnimationInputConsumer.mWindowHandle.token) { + // Avoid removing input focus from recentsAnimationInputConsumer. + // When the recents animation input consumer has the input focus, + // mInputFocus does not match to mDisplayContent.mCurrentFocus. Making it to be + // a special case, that do not remove the input focus from it when + // mDisplayContent.mCurrentFocus is null. This special case should be removed + // once recentAnimationInputConsumer is removed. + return; + } // When an app is focused, but its window is not showing yet, remove the input focus - // from the current window. + // from the current window. This enforces the input focus to match + // mDisplayContent.mCurrentFocus. However, if more special cases are discovered that + // the input focus and mDisplayContent.mCurrentFocus are expected to mismatch, + // the whole logic of how and when to revoke focus needs to be checked. if (mDisplayContent.mFocusedApp != null && mInputFocus != null) { ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "App %s is focused," + " but the window is not ready. Start a transaction to remove focus from" diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index ad2767c41e82..d02ad992c7e8 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -22,6 +22,7 @@ import android.content.Context; import android.graphics.Color; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -156,6 +157,7 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and * the framework implementation will be used to determine the aspect ratio. */ + @VisibleForTesting void setFixedOrientationLetterboxAspectRatio(float aspectRatio) { mFixedOrientationLetterboxAspectRatio = aspectRatio; } @@ -164,6 +166,7 @@ final class LetterboxConfiguration { * Resets the aspect ratio of letterbox for fixed orientation to {@link * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}. */ + @VisibleForTesting void resetFixedOrientationLetterboxAspectRatio() { mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); @@ -177,25 +180,6 @@ final class LetterboxConfiguration { } /** - * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, - * both it and a value of {@link - * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and - * corners of the activity won't be rounded. - */ - void setLetterboxActivityCornersRadius(int cornersRadius) { - mLetterboxActivityCornersRadius = cornersRadius; - } - - /** - * Resets corners raidus for activities presented in the letterbox mode to {@link - * com.android.internal.R.integer.config_letterboxActivityCornersRadius}. - */ - void resetLetterboxActivityCornersRadius() { - mLetterboxActivityCornersRadius = mContext.getResources().getInteger( - com.android.internal.R.integer.config_letterboxActivityCornersRadius); - } - - /** * Whether corners of letterboxed activities are rounded. */ boolean isLetterboxActivityCornersRounded() { @@ -226,34 +210,6 @@ final class LetterboxConfiguration { return Color.valueOf(mContext.getResources().getColor(colorId)); } - - /** - * Sets color of letterbox background which is used when {@link - * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. - */ - void setLetterboxBackgroundColor(Color color) { - mLetterboxBackgroundColorOverride = color; - } - - /** - * Sets color ID of letterbox background which is used when {@link - * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. - */ - void setLetterboxBackgroundColorResourceId(int colorId) { - mLetterboxBackgroundColorResourceIdOverride = colorId; - } - - /** - * Resets color of letterbox background to {@link - * com.android.internal.R.color.config_letterboxBackgroundColor}. - */ - void resetLetterboxBackgroundColor() { - mLetterboxBackgroundColorOverride = null; - mLetterboxBackgroundColorResourceIdOverride = null; - } - /** * Gets {@link LetterboxBackgroundType} specified in {@link * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command. @@ -263,19 +219,6 @@ final class LetterboxConfiguration { return mLetterboxBackgroundType; } - /** Sets letterbox background type. */ - void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) { - mLetterboxBackgroundType = backgroundType; - } - - /** - * Resets cletterbox background type to {@link - * com.android.internal.R.integer.config_letterboxBackgroundType}. - */ - void resetLetterboxBackgroundType() { - mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext); - } - /** Returns a string representing the given {@link LetterboxBackgroundType}. */ static String letterboxBackgroundTypeToString( @LetterboxBackgroundType int backgroundType) { @@ -305,27 +248,6 @@ final class LetterboxConfiguration { } /** - * Overrides alpha of a black scrim shown over wallpaper for {@link - * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}. - * - * <p>If given value is < 0 or >= 1, both it and a value of {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored - * and 0.0 (transparent) is instead. - */ - void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) { - mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha; - } - - /** - * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}. - */ - void resetLetterboxBackgroundWallpaperDarkScrimAlpha() { - mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); - } - - /** * Gets alpha of a black scrim shown over wallpaper letterbox background. */ float getLetterboxBackgroundWallpaperDarkScrimAlpha() { @@ -333,28 +255,6 @@ final class LetterboxConfiguration { } /** - * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in - * {@link mLetterboxBackgroundType}. - * - * <p> If given value <= 0, both it and a value of {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored - * and 0 is used instead. - */ - void setLetterboxBackgroundWallpaperBlurRadius(int radius) { - mLetterboxBackgroundWallpaperBlurRadius = radius; - } - - /** - * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link - * mLetterboxBackgroundType} to {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}. - */ - void resetLetterboxBackgroundWallpaperBlurRadius() { - mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius); - } - - /** * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link * mLetterboxBackgroundType}. */ @@ -381,6 +281,7 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and * central position (0.5) is used. */ + @VisibleForTesting void setLetterboxHorizontalPositionMultiplier(float multiplier) { mLetterboxHorizontalPositionMultiplier = multiplier; } @@ -389,6 +290,7 @@ final class LetterboxConfiguration { * Resets horizontal position of a center of the letterboxed app window to {@link * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}. */ + @VisibleForTesting void resetLetterboxHorizontalPositionMultiplier() { mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat( com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier); @@ -406,6 +308,7 @@ final class LetterboxConfiguration { * Overrides whether reachability repositioning is allowed for letterboxed fullscreen apps in * landscape device orientation. */ + @VisibleForTesting void setIsReachabilityEnabled(boolean enabled) { mIsReachabilityEnabled = enabled; } @@ -414,6 +317,7 @@ final class LetterboxConfiguration { * Resets whether reachability repositioning is allowed for letterboxed fullscreen apps in * landscape device orientation to {@link R.bool.config_letterboxIsReachabilityEnabled}. */ + @VisibleForTesting void resetIsReachabilityEnabled() { mIsReachabilityEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsReachabilityEnabled); @@ -429,22 +333,6 @@ final class LetterboxConfiguration { return mDefaultPositionForReachability; } - /** - * Overrides default horizonal position of the letterboxed app window when reachability - * is enabled. - */ - void setDefaultPositionForReachability(@LetterboxReachabilityPosition int position) { - mDefaultPositionForReachability = position; - } - - /** - * Resets default horizontal position of the letterboxed app window when reachability is - * enabled to {@link R.integer.config_letterboxDefaultPositionForReachability}. - */ - void resetDefaultPositionForReachability() { - mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext); - } - @LetterboxReachabilityPosition private static int readLetterboxReachabilityPositionFromConfig(Context context) { int position = context.getResources().getInteger( @@ -516,17 +404,8 @@ final class LetterboxConfiguration { /** * Overrides whether education is allowed for letterboxed fullscreen apps. */ + @VisibleForTesting void setIsEducationEnabled(boolean enabled) { mIsEducationEnabled = enabled; } - - /** - * Resets whether education is allowed for letterboxed fullscreen apps to - * {@link R.bool.config_letterboxIsEducationEnabled}. - */ - void resetIsEducationEnabled() { - mIsEducationEnabled = mContext.getResources().getBoolean( - R.bool.config_letterboxIsEducationEnabled); - } - } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 160fc95f3f7c..7a055d2948ad 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -64,6 +64,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.telephony.CellBroadcastUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; @@ -392,6 +393,10 @@ public class LockTaskController { return false; } + if (isWirelessEmergencyAlert(intent)) { + return false; + } + return !(isTaskAuthAllowlisted(taskAuth) || mLockTaskModeTasks.isEmpty()); } @@ -424,6 +429,25 @@ public class LockTaskController { return isPackageAllowlisted(userId, packageName); } + private boolean isWirelessEmergencyAlert(Intent intent) { + if (intent == null) { + return false; + } + + final ComponentName cellBroadcastAlertDialogComponentName = + CellBroadcastUtils.getDefaultCellBroadcastAlertDialogComponent(mContext); + + if (cellBroadcastAlertDialogComponentName == null) { + return false; + } + + if (cellBroadcastAlertDialogComponentName.equals(intent.getComponent())) { + return true; + } + + return false; + } + private boolean isEmergencyCallIntent(Intent intent) { if (intent == null) { return false; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index c0dff14e5de5..0aab18604528 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1999,7 +1999,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask(); if (rootPinnedTask != null) { transitionController.collect(rootPinnedTask); - rootPinnedTask.dismissPip(); + // The new ActivityRecord should replace the existing PiP, so it's more desirable + // that the old PiP disappears instead of turning to full-screen at the same time, + // as the Task#dismissPip is trying to do. + removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED); } // Set a transition to ensure that we don't immediately try and update the visibility diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2f00bc821678..a480c37fbcf3 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1584,6 +1584,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return true; } + void forAllWindowContainers(Consumer<WindowContainer> callback) { + callback.accept(this); + final int count = mChildren.size(); + for (int i = 0; i < count; i++) { + mChildren.get(i).forAllWindowContainers(callback); + } + } + /** * For all windows at or below this container call the callback. * @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8f1f7ece897b..7a5480401de8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -118,6 +118,7 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; @@ -3052,13 +3053,22 @@ public class WindowManagerService extends IWindowManager.Stub } } + void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) { if (mRecentsAnimationController != null) { final RecentsAnimationController controller = mRecentsAnimationController; mRecentsAnimationController = null; controller.cleanupAnimation(reorderMode); - // TODO(mult-display): currently only default display support recents animation. - getDefaultDisplayContentLocked().mAppTransition.updateBooster(); + // TODO(multi-display): currently only default display support recents animation. + final DisplayContent dc = getDefaultDisplayContentLocked(); + if (dc.mAppTransition.isTransitionSet()) { + dc.mSkipAppTransitionAnimation = true; + } + dc.forAllWindowContainers((wc) -> { + if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) { + wc.cancelAnimation(); + } + }); } } @@ -5694,25 +5704,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) { - if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS); - } - - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.setSandboxDisplayApis(sandboxDisplayApis); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - /** The global settings only apply to default display. */ private boolean applyForcedPropertiesForDefaultDisplay() { boolean changed = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 5a2f28f4a365..34c93482ecfe 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -19,16 +19,6 @@ package com.android.server.wm; import static android.os.Build.IS_USER; import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_LEFT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_RIGHT; - -import android.content.res.Resources.NotFoundException; -import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.ParcelFileDescriptor; @@ -46,8 +36,6 @@ import com.android.internal.os.ByteTransferPipe; import com.android.internal.protolog.ProtoLogImpl; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; -import com.android.server.wm.LetterboxConfiguration.LetterboxReachabilityPosition; import java.io.IOException; import java.io.PrintWriter; @@ -70,12 +58,10 @@ public class WindowManagerShellCommand extends ShellCommand { // Internal service impl -- must perform security checks before touching. private final WindowManagerService mInternal; - private final LetterboxConfiguration mLetterboxConfiguration; public WindowManagerShellCommand(WindowManagerService service) { mInterface = service; mInternal = service; - mLetterboxConfiguration = service.mLetterboxConfiguration; } @Override @@ -127,14 +113,6 @@ public class WindowManagerShellCommand extends ShellCommand { return runGetIgnoreOrientationRequest(pw); case "dump-visible-window-views": return runDumpVisibleWindowViews(pw); - case "set-letterbox-style": - return runSetLetterboxStyle(pw); - case "get-letterbox-style": - return runGetLetterboxStyle(pw); - case "reset-letterbox-style": - return runResetLetterboxStyle(pw); - case "set-sandbox-display-apis": - return runSandboxDisplayApis(pw); case "set-multi-window-config": return runSetMultiWindowConfig(); case "get-multi-window-config": @@ -353,37 +331,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - /** - * Override display size and metrics to reflect the DisplayArea of the calling activity. - */ - private int runSandboxDisplayApis(PrintWriter pw) throws RemoteException { - int displayId = Display.DEFAULT_DISPLAY; - String arg = getNextArgRequired(); - if ("-d".equals(arg)) { - displayId = Integer.parseInt(getNextArgRequired()); - arg = getNextArgRequired(); - } - - final boolean sandboxDisplayApis; - switch (arg) { - case "true": - case "1": - sandboxDisplayApis = true; - break; - case "false": - case "0": - sandboxDisplayApis = false; - break; - default: - getErrPrintWriter().println("Error: expecting true, 1, false, 0, but we " - + "get " + arg); - return -1; - } - - mInternal.setSandboxDisplayApis(displayId, sandboxDisplayApis); - return 0; - } - private int runDismissKeyguard(PrintWriter pw) throws RemoteException { mInterface.dismissKeyguard(null /* callback */, null /* message */); return 0; @@ -606,347 +553,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException { - final float aspectRatio; - try { - String arg = getNextArgRequired(); - aspectRatio = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad aspect ratio format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: aspect ratio should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio); - } - return 0; - } - - private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException { - final int cornersRadius; - try { - String arg = getNextArgRequired(); - cornersRadius = Integer.parseInt(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad corners radius format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: corners radius should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius); - } - return 0; - } - - private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException { - @LetterboxBackgroundType final int backgroundType; - try { - String arg = getNextArgRequired(); - switch (arg) { - case "solid_color": - backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR; - break; - case "app_color_background": - backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; - break; - case "app_color_background_floating": - backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; - break; - case "wallpaper": - backgroundType = LETTERBOX_BACKGROUND_WALLPAPER; - break; - default: - getErrPrintWriter().println( - "Error: 'solid_color', 'app_color_background' or " - + "'wallpaper' should be provided as an argument"); - return -1; - } - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'solid_color', 'app_color_background' or " - + "'wallpaper' should be provided as an argument" + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType); - } - return 0; - } - - private int runSetLetterboxBackgroundColorResource(PrintWriter pw) throws RemoteException { - final int colorId; - try { - String arg = getNextArgRequired(); - colorId = mInternal.mContext.getResources() - .getIdentifier(arg, "color", "com.android.internal"); - } catch (NotFoundException e) { - getErrPrintWriter().println( - "Error: color in '@android:color/resource_name' format should be provided as " - + "an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundColorResourceId(colorId); - } - return 0; - } - - private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException { - final Color color; - try { - String arg = getNextArgRequired(); - color = Color.valueOf(Color.parseColor(arg)); - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: color in #RRGGBB format should be provided as " - + "an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundColor(color); - } - return 0; - } - - private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw) - throws RemoteException { - final int radius; - try { - String arg = getNextArgRequired(); - radius = Integer.parseInt(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: blur radius format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: blur radius should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius); - } - return 0; - } - - private int runSetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw) - throws RemoteException { - final float alpha; - try { - String arg = getNextArgRequired(); - alpha = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad alpha format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: alpha should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); - } - return 0; - } - - private int runSetLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException { - final float multiplier; - try { - String arg = getNextArgRequired(); - multiplier = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad multiplier format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: multiplier should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier); - } - return 0; - } - - private int runSetLetterboxIsReachabilityEnabled(PrintWriter pw) throws RemoteException { - String arg = getNextArg(); - final boolean enabled; - switch (arg) { - case "true": - case "1": - enabled = true; - break; - case "false": - case "0": - enabled = false; - break; - default: - getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg); - return -1; - } - - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setIsReachabilityEnabled(enabled); - } - return 0; - } - - private int runSetLetterboxDefaultPositionForReachability(PrintWriter pw) - throws RemoteException { - @LetterboxReachabilityPosition final int position; - try { - String arg = getNextArgRequired(); - switch (arg) { - case "left": - position = LETTERBOX_REACHABILITY_POSITION_LEFT; - break; - case "center": - position = LETTERBOX_REACHABILITY_POSITION_CENTER; - break; - case "right": - position = LETTERBOX_REACHABILITY_POSITION_RIGHT; - break; - default: - getErrPrintWriter().println( - "Error: 'left', 'center' or 'right' are expected as an argument"); - return -1; - } - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'left', 'center' or 'right' are expected as an argument" + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setDefaultPositionForReachability(position); - } - return 0; - } - - private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException { - String arg = getNextArg(); - final boolean enabled; - switch (arg) { - case "true": - case "1": - enabled = true; - break; - case "false": - case "0": - enabled = false; - break; - default: - getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg); - return -1; - } - - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setIsEducationEnabled(enabled); - } - return 0; - } - - private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException { - if (peekNextArg() == null) { - getErrPrintWriter().println("Error: No arguments provided."); - } - while (peekNextArg() != null) { - String arg = getNextArg(); - switch (arg) { - case "--aspectRatio": - runSetFixedOrientationLetterboxAspectRatio(pw); - break; - case "--cornerRadius": - runSetLetterboxActivityCornersRadius(pw); - break; - case "--backgroundType": - runSetLetterboxBackgroundType(pw); - break; - case "--backgroundColor": - runSetLetterboxBackgroundColor(pw); - break; - case "--backgroundColorResource": - runSetLetterboxBackgroundColorResource(pw); - break; - case "--wallpaperBlurRadius": - runSetLetterboxBackgroundWallpaperBlurRadius(pw); - break; - case "--wallpaperDarkScrimAlpha": - runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw); - break; - case "--horizontalPositionMultiplier": - runSetLetterboxHorizontalPositionMultiplier(pw); - break; - case "--isReachabilityEnabled": - runSetLetterboxIsReachabilityEnabled(pw); - break; - case "--defaultPositionForReachability": - runSetLetterboxDefaultPositionForReachability(pw); - break; - case "--isEducationEnabled": - runSetLetterboxIsEducationEnabled(pw); - break; - default: - getErrPrintWriter().println( - "Error: Unrecognized letterbox style option: " + arg); - return -1; - } - } - return 0; - } - - private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException { - if (peekNextArg() == null) { - resetLetterboxStyle(); - } - synchronized (mInternal.mGlobalLock) { - while (peekNextArg() != null) { - String arg = getNextArg(); - switch (arg) { - case "aspectRatio": - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - break; - case "cornerRadius": - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - break; - case "backgroundType": - mLetterboxConfiguration.resetLetterboxBackgroundType(); - break; - case "backgroundColor": - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - break; - case "wallpaperBlurRadius": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius(); - break; - case "wallpaperDarkScrimAlpha": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - break; - case "horizontalPositionMultiplier": - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - break; - case "isReachabilityEnabled": - mLetterboxConfiguration.getIsReachabilityEnabled(); - break; - case "defaultPositionForReachability": - mLetterboxConfiguration.getDefaultPositionForReachability(); - break; - case "isEducationEnabled": - mLetterboxConfiguration.getIsEducationEnabled(); - break; - default: - getErrPrintWriter().println( - "Error: Unrecognized letterbox style option: " + arg); - return -1; - } - } - } - return 0; - } - private int runSetMultiWindowConfig() { if (peekNextArg() == null) { getErrPrintWriter().println("Error: No arguments provided."); @@ -1021,50 +627,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private void resetLetterboxStyle() { - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundType(); - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - mLetterboxConfiguration.resetIsReachabilityEnabled(); - mLetterboxConfiguration.resetDefaultPositionForReachability(); - mLetterboxConfiguration.resetIsEducationEnabled(); - } - } - - private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - pw.println("Corner radius: " - + mLetterboxConfiguration.getLetterboxActivityCornersRadius()); - pw.println("Horizontal position multiplier: " - + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier()); - pw.println("Aspect ratio: " - + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); - pw.println("Is reachability enabled: " - + mLetterboxConfiguration.getIsReachabilityEnabled()); - pw.println("Default position for reachability: " - + LetterboxConfiguration.letterboxReachabilityPositionToString( - mLetterboxConfiguration.getDefaultPositionForReachability())); - pw.println("Is education enabled: " - + mLetterboxConfiguration.getIsEducationEnabled()); - - pw.println("Background type: " - + LetterboxConfiguration.letterboxBackgroundTypeToString( - mLetterboxConfiguration.getLetterboxBackgroundType())); - pw.println(" Background color: " + Integer.toHexString( - mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb())); - pw.println(" Wallpaper blur radius: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius()); - pw.println(" Wallpaper dark scrim alpha: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); - } - return 0; - } - private int runReset(PrintWriter pw) throws RemoteException { int displayId = getDisplayId(getNextArg()); @@ -1089,12 +651,6 @@ public class WindowManagerShellCommand extends ShellCommand { // set-ignore-orientation-request mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */); - // set-letterbox-style - resetLetterboxStyle(); - - // set-sandbox-display-apis - mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true); - // set-multi-window-config runResetMultiWindowConfig(); @@ -1129,12 +685,7 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]"); pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] "); pw.println(" If app requested orientation should be ignored."); - pw.println(" set-sandbox-display-apis [true|1|false|0]"); - pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect "); - pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or"); - pw.println(" Size Compat Mode."); - printLetterboxHelp(pw); printMultiWindowConfigHelp(pw); pw.println(" reset [-d DISPLAY_ID]"); @@ -1147,63 +698,6 @@ public class WindowManagerShellCommand extends ShellCommand { } } - private void printLetterboxHelp(PrintWriter pw) { - pw.println(" set-letterbox-style"); - pw.println(" Sets letterbox style using the following options:"); - pw.println(" --aspectRatio aspectRatio"); - pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= " - + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); - pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will"); - pw.println(" be ignored and framework implementation will determine aspect ratio."); - pw.println(" --cornerRadius radius"); - pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,"); - pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be"); - pw.println(" ignored and corners of the activity won't be rounded."); - pw.println(" --backgroundType [reset|solid_color|app_color_background"); - pw.println(" |app_color_background_floating|wallpaper]"); - pw.println(" Type of background used in the letterbox mode."); - pw.println(" --backgroundColor color"); - pw.println(" Color of letterbox which is be used when letterbox background type"); - pw.println(" is 'solid-color'. Use (set)get-letterbox-style to check and control"); - pw.println(" letterbox background type. See Color#parseColor for allowed color"); - pw.println(" formats (#RRGGBB and some colors by name, e.g. magenta or olive)."); - pw.println(" --backgroundColorResource resource_name"); - pw.println(" Color resource name of letterbox background which is used when"); - pw.println(" background type is 'solid-color'. Use (set)get-letterbox-style to"); - pw.println(" check and control background type. Parameter is a color resource"); - pw.println(" name, for example, @android:color/system_accent2_50."); - pw.println(" --wallpaperBlurRadius radius"); - pw.println(" Blur radius for 'wallpaper' letterbox background. If radius <= 0"); - pw.println(" both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius"); - pw.println(" are ignored and 0 is used."); - pw.println(" --wallpaperDarkScrimAlpha alpha"); - pw.println(" Alpha of a black translucent scrim shown over 'wallpaper'"); - pw.println(" letterbox background. If alpha < 0 or >= 1 both it and"); - pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored"); - pw.println(" and 0.0 (transparent) is used instead."); - pw.println(" --horizontalPositionMultiplier multiplier"); - pw.println(" Horizontal position of app window center. If multiplier < 0 or > 1,"); - pw.println(" both it and R.dimen.config_letterboxHorizontalPositionMultiplier"); - pw.println(" are ignored and central position (0.5) is used."); - pw.println(" --isReachabilityEnabled [true|1|false|0]"); - pw.println(" Whether reachability repositioning is allowed for letterboxed"); - pw.println(" fullscreen apps in landscape device orientation."); - pw.println(" --defaultPositionForReachability [left|center|right]"); - pw.println(" Default horizontal position of app window when reachability is."); - pw.println(" enabled."); - pw.println(" --isEducationEnabled [true|1|false|0]"); - pw.println(" Whether education is allowed for letterboxed fullscreen apps."); - pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType"); - pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); - pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled"); - pw.println(" isEducationEnabled||defaultPositionMultiplierForReachability]"); - pw.println(" Resets overrides to default values for specified properties separated"); - pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); - pw.println(" If no arguments provided, all values will be reset."); - pw.println(" get-letterbox-style"); - pw.println(" Prints letterbox style configuration."); - } - private void printMultiWindowConfigHelp(PrintWriter pw) { pw.println(" set-multi-window-config"); pw.println(" Sets options to determine if activity should be shown in multi window:"); diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index 93152f2ea1b7..dbc1a00ce274 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -66,13 +66,13 @@ using android::base::unique_fd; // Defines the maximum amount of VMAs we can send per process_madvise syscall. // Currently this is set to UIO_MAXIOV which is the maximum segments allowed by // iovec implementation used by process_madvise syscall -#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV +#define MAX_VMAS_PER_BATCH UIO_MAXIOV // Maximum bytes that we can send per process_madvise syscall once this limit // is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT // limit is imposed by iovec implementation. However, if you want to use a smaller -// limit, it has to be a page aligned value, otherwise, compaction would fail. -#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT +// limit, it has to be a page aligned value. +#define MAX_BYTES_PER_BATCH MAX_RW_COUNT // Selected a high enough number to avoid clashing with linux errno codes #define ERROR_COMPACTION_CANCELLED -1000 @@ -83,6 +83,180 @@ namespace android { // before starting next VMA batch static std::atomic<bool> cancelRunningCompaction; +// A VmaBatch represents a set of VMAs that can be processed +// as VMAs are processed by client code it is expected that the +// VMAs get consumed which means they are discarded as they are +// processed so that the first element always is the next element +// to be sent +struct VmaBatch { + struct iovec* vmas; + // total amount of VMAs to reach the end of iovec + size_t totalVmas; + // total amount of bytes that are remaining within iovec + uint64_t totalBytes; +}; + +// Advances the iterator by the specified amount of bytes. +// This is used to remove already processed or no longer +// needed parts of the batch. +// Returns total bytes consumed +uint64_t consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) { + if (CC_UNLIKELY(bytesToConsume) < 0) { + LOG(ERROR) << "Cannot consume negative bytes for VMA batch !"; + return 0; + } + + if (CC_UNLIKELY(bytesToConsume > batch.totalBytes)) { + // Avoid consuming more bytes than available + bytesToConsume = batch.totalBytes; + } + + uint64_t bytesConsumed = 0; + while (bytesConsumed < bytesToConsume) { + if (CC_UNLIKELY(batch.totalVmas > 0)) { + // No more vmas to consume + break; + } + if (CC_UNLIKELY(bytesConsumed + batch.vmas[0].iov_len > bytesToConsume)) { + // This vma can't be fully consumed, do it partially. + uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed; + bytesConsumed += bytesLeftToConsume; + batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume); + batch.vmas[0].iov_len -= bytesLeftToConsume; + batch.totalBytes -= bytesLeftToConsume; + return bytesConsumed; + } + // This vma can be fully consumed + bytesConsumed += batch.vmas[0].iov_len; + batch.totalBytes -= batch.vmas[0].iov_len; + --batch.totalVmas; + ++batch.vmas; + } + + return bytesConsumed; +} + +// given a source of vmas this class will act as a factory +// of VmaBatch objects and it will allow generating batches +// until there are no more left in the source vector. +// Note: the class does not actually modify the given +// vmas vector, instead it iterates on it until the end. +class VmaBatchCreator { + const std::vector<Vma>* sourceVmas; + // This is the destination array where batched VMAs will be stored + // it gets encapsulated into a VmaBatch which is the object + // meant to be used by client code. + struct iovec* destVmas; + + // Parameters to keep track of the iterator on the source vmas + int currentIndex_; + uint64_t currentOffset_; + +public: + VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec) + : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {} + + int currentIndex() { return currentIndex_; } + uint64_t currentOffset() { return currentOffset_; } + + // Generates a batch and moves the iterator on the source vmas + // past the last VMA in the batch. + // Returns true on success, false on failure + bool createNextBatch(VmaBatch& batch) { + if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) { + return false; + } + + const std::vector<Vma>& vmas = *sourceVmas; + batch.vmas = destVmas; + uint64_t totalBytesInBatch = 0; + int indexInBatch = 0; + + // Add VMAs to the batch up until we consumed all the VMAs or + // reached any imposed limit of VMAs per batch. + while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) { + uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_; + uint64_t vmaSize = vmas[currentIndex_].end - vmaStart; + uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch; + + batch.vmas[indexInBatch].iov_base = (void*)vmaStart; + + if (vmaSize > bytesAvailableInBatch) { + // VMA would exceed the max available bytes in batch + // clamp with available bytes and finish batch. + vmaSize = bytesAvailableInBatch; + currentOffset_ += bytesAvailableInBatch; + } + + batch.vmas[indexInBatch].iov_len = vmaSize; + totalBytesInBatch += vmaSize; + + ++indexInBatch; + if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) { + // Reached max bytes quota so this marks + // the end of the batch + if (CC_UNLIKELY(vmaSize == (vmas[currentIndex_].end - vmaStart))) { + // we reached max bytes exactly at the end of the vma + // so advance to next one + currentOffset_ = 0; + ++currentIndex_; + } + break; + } + // Fully finished current VMA, move to next one + currentOffset_ = 0; + ++currentIndex_; + } + batch.totalVmas = indexInBatch; + batch.totalBytes = totalBytesInBatch; + if (batch.totalVmas == 0 || batch.totalBytes == 0) { + // This is an empty batch, mark as failed creating. + return false; + } + return true; + } +}; + +// Madvise a set of VMAs given in a batch for a specific process +// The total number of bytes successfully madvised will be set on +// outBytesProcessed. +// Returns 0 on success and standard linux -errno code returned by +// process_madvise on failure +int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType, + uint64_t* outBytesProcessed) { + if (batch.totalVmas == 0 || batch.totalBytes == 0) { + // No VMAs in Batch, skip. + *outBytesProcessed = 0; + return 0; + } + + ATRACE_BEGIN(StringPrintf("Madvise %d: %zu VMAs.", madviseType, batch.totalVmas).c_str()); + int64_t bytesProcessedInSend = + process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0); + ATRACE_END(); + if (CC_UNLIKELY(bytesProcessedInSend == -1)) { + bytesProcessedInSend = 0; + if (errno != EINVAL) { + // Forward irrecoverable errors and bail out compaction + *outBytesProcessed = 0; + return -errno; + } + } + if (bytesProcessedInSend == 0) { + // When we find a VMA with error, fully consume it as it + // is extremely expensive to iterate on its pages one by one + bytesProcessedInSend = batch.vmas[0].iov_len; + } else if (bytesProcessedInSend < batch.totalBytes) { + // Partially processed the bytes requested + // skip last page which is where it failed. + bytesProcessedInSend += PAGE_SIZE; + } + bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend); + + *outBytesProcessed = bytesProcessedInSend; + return 0; +} + // Legacy method for compacting processes, any new code should // use compactProcess instead. static inline void compactProcessProcfs(int pid, const std::string& compactionType) { @@ -96,8 +270,6 @@ static inline void compactProcessProcfs(int pid, const std::string& compactionTy // If any VMA fails compaction due to -EINVAL it will be skipped and continue. // However, if it fails for any other reason, it will bail out and forward the error static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) { - static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION]; - if (vmas.empty()) { return 0; } @@ -108,13 +280,16 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT return -errno; } - int64_t totalBytesProcessed = 0; + struct iovec destVmas[MAX_VMAS_PER_BATCH]; + + VmaBatch batch; + VmaBatchCreator batcher(&vmas, destVmas); - int64_t vmaOffset = 0; - for (int iVma = 0; iVma < vmas.size();) { - uint64_t bytesSentToCompact = 0; - int iVec = 0; - while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) { + int64_t totalBytesProcessed = 0; + while (batcher.createNextBatch(batch)) { + uint64_t bytesProcessedInSend; + ScopedTrace batchTrace(ATRACE_TAG, "VMA Batch"); + do { if (CC_UNLIKELY(cancelRunningCompaction.load())) { // There could be a significant delay between when a compaction // is requested and when it is handled during this time our @@ -124,50 +299,18 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT StringPrintf("Cancelled compaction for %d", pid).c_str()); return ERROR_COMPACTION_CANCELLED; } - - uint64_t vmaStart = vmas[iVma].start + vmaOffset; - uint64_t vmaSize = vmas[iVma].end - vmaStart; - if (vmaSize == 0) { - goto next_vma; - } - vmasToKernel[iVec].iov_base = (void*)vmaStart; - if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) { - // Exceeded the max bytes that could be sent, so clamp - // the end to avoid exceeding limit and issue compaction - vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact; + int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend); + if (error < 0) { + // Returns standard linux errno code + return error; } - - vmasToKernel[iVec].iov_len = vmaSize; - bytesSentToCompact += vmaSize; - ++iVec; - if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) { - // Ran out of bytes within iovec, dispatch compaction. - vmaOffset += vmaSize; + if (CC_UNLIKELY(bytesProcessedInSend == 0)) { + // This means there was a problem consuming bytes, + // bail out since no forward progress can be made with this batch break; } - - next_vma: - // Finished current VMA, and have more bytes remaining - vmaOffset = 0; - ++iVma; - } - - ATRACE_BEGIN(StringPrintf("Compact %d VMAs", iVec).c_str()); - auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0); - ATRACE_END(); - - if (CC_UNLIKELY(bytesProcessed == -1)) { - if (errno == EINVAL) { - // This error is somewhat common due to an unevictable VMA if this is - // the case silently skip the bad VMA and continue compacting the rest. - continue; - } else { - // Forward irrecoverable errors and bail out compaction - return -errno; - } - } - - totalBytesProcessed += bytesProcessed; + totalBytesProcessed += bytesProcessedInSend; + } while (batch.totalBytes > 0 && batch.totalVmas > 0); } return totalBytesProcessed; @@ -203,6 +346,7 @@ static int getAnyPageAdvice(const Vma& vma) { static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) { cancelRunningCompaction.store(false); + ATRACE_BEGIN("CollectVmas"); ProcMemInfo meminfo(pid); std::vector<Vma> pageoutVmas, coldVmas; auto vmaCollectorCb = [&coldVmas,&pageoutVmas,&vmaToAdviseFunc](const Vma& vma) { @@ -217,6 +361,7 @@ static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) { } }; meminfo.ForEachVmaFromMaps(vmaCollectorCb); + ATRACE_END(); int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT); if (pageoutBytes < 0) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 32adac7f282b..287fb8219650 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -106,7 +106,6 @@ static struct { jmethodID interceptMotionBeforeQueueingNonInteractive; jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; - jmethodID checkInjectEventsPermission; jmethodID onPointerDisplayIdChanged; jmethodID onPointerDownOutsideFocus; jmethodID getVirtualKeyQuietTimeMillis; @@ -333,7 +332,6 @@ public: bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override; void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; - bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; void setPointerCapture(const PointerCaptureRequest& request) override; void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; @@ -1380,19 +1378,6 @@ void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, android_server_PowerManagerService_userActivity(eventTime, eventType, displayId); } -bool NativeInputManager::checkInjectEventsPermissionNonReentrant(int32_t injectorPid, - int32_t injectorUid) { - ATRACE_CALL(); - JNIEnv* env = jniEnv(); - jboolean result = - env->CallBooleanMethod(mServiceObj, gServiceClassInfo.checkInjectEventsPermission, - injectorPid, injectorUid); - if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) { - result = false; - } - return result; -} - void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1709,10 +1694,11 @@ static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jobject nativeImplOb } static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj, - jint injectorPid, jint injectorUid, jint syncMode, + jboolean injectIntoUid, jint uid, jint syncMode, jint timeoutMillis, jint policyFlags) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt; // static_cast is safe because the value was already checked at the Java layer InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode); @@ -1725,8 +1711,7 @@ static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject i } const InputEventInjectionResult result = - im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid, - injectorUid, mode, + im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode, std::chrono::milliseconds( timeoutMillis), uint32_t(policyFlags)); @@ -1739,8 +1724,8 @@ static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject i } const InputEventInjectionResult result = - im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid, - injectorUid, mode, + im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid, + mode, std::chrono::milliseconds( timeoutMillis), uint32_t(policyFlags)); @@ -2345,7 +2330,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setMaximumObscuringOpacityForTouch", "(F)V", (void*)nativeSetMaximumObscuringOpacityForTouch}, {"setBlockUntrustedTouchesMode", "(I)V", (void*)nativeSetBlockUntrustedTouchesMode}, - {"injectInputEvent", "(Landroid/view/InputEvent;IIIII)I", (void*)nativeInjectInputEvent}, + {"injectInputEvent", "(Landroid/view/InputEvent;ZIIII)I", (void*)nativeInjectInputEvent}, {"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;", (void*)nativeVerifyInputEvent}, {"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock}, @@ -2492,9 +2477,6 @@ int register_android_server_InputManager(JNIEnv* env) { "dispatchUnhandledKey", "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;"); - GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz, - "checkInjectEventsPermission", "(II)Z"); - GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged", "(IFF)V"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index 3912991a41ed..0c69067ab131 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -157,13 +157,14 @@ class ActiveAdmin { private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist"; private static final String TAG_SSID_DENYLIST = "ssid-denylist"; private static final String TAG_SSID = "ssid"; - private static final String ATTR_VALUE = "value"; - private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; - private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIGS = "preferential_network_service_configs"; private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIG = "preferential_network_service_config"; + private static final String TAG_PROTECTED_PACKAGES = "protected_packages"; + private static final String ATTR_VALUE = "value"; + private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; + private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; DeviceAdminInfo info; @@ -253,6 +254,9 @@ class ActiveAdmin { // List of package names to keep cached. List<String> keepUninstalledPackages; + // List of packages for which the user cannot invoke "clear data" or "force stop". + List<String> protectedPackages; + // Wi-Fi SSID restriction policy. WifiSsidPolicy mWifiSsidPolicy; @@ -505,6 +509,7 @@ class ActiveAdmin { permittedNotificationListeners); writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages); writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages); + writePackageListToXml(out, TAG_PROTECTED_PACKAGES, protectedPackages); if (hasUserRestrictions()) { UserRestrictionsUtils.writeRestrictions( out, userRestrictions, TAG_USER_RESTRICTIONS); @@ -771,6 +776,8 @@ class ActiveAdmin { keepUninstalledPackages = readPackageList(parser, tag); } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) { meteredDisabledPackages = readPackageList(parser, tag); + } else if (TAG_PROTECTED_PACKAGES.equals(tag)) { + protectedPackages = readPackageList(parser, tag); } else if (TAG_USER_RESTRICTIONS.equals(tag)) { userRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) { @@ -1210,6 +1217,16 @@ class ActiveAdmin { pw.println(keepUninstalledPackages); } + if (meteredDisabledPackages != null) { + pw.print("meteredDisabledPackages="); + pw.println(meteredDisabledPackages); + } + + if (protectedPackages != null) { + pw.print("protectedPackages="); + pw.println(protectedPackages); + } + pw.print("organizationColor="); pw.println(organizationColor); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java index 48a436f15803..fd97db23f01a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManager; @@ -129,8 +130,10 @@ class DevicePolicyData { // This is the list of component allowed to start lock task mode. List<String> mLockTaskPackages = new ArrayList<>(); - // List of packages protected by device owner - List<String> mUserControlDisabledPackages = new ArrayList<>(); + /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */ + @Deprecated + @Nullable + List<String> mUserControlDisabledPackages; // Bitfield of feature flags to be enabled during LockTask mode. // We default on the power button menu, in order to be consistent with pre-P behaviour. @@ -364,13 +367,6 @@ class DevicePolicyData { out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT); } - for (int i = 0, size = policyData.mUserControlDisabledPackages.size(); i < size; i++) { - String packageName = policyData.mUserControlDisabledPackages.get(i); - out.startTag(null, TAG_PROTECTED_PACKAGES); - out.attribute(null, ATTR_NAME, packageName); - out.endTag(null, TAG_PROTECTED_PACKAGES); - } - if (policyData.mAppsSuspended) { out.startTag(null, TAG_APPS_SUSPENDED); out.attributeBoolean(null, ATTR_VALUE, policyData.mAppsSuspended); @@ -473,7 +469,7 @@ class DevicePolicyData { policy.mAdminMap.clear(); policy.mAffiliationIds.clear(); policy.mOwnerInstalledCaCerts.clear(); - policy.mUserControlDisabledPackages.clear(); + policy.mUserControlDisabledPackages = null; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -561,15 +557,19 @@ class DevicePolicyData { policy.mCurrentInputMethodSet = true; } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) { policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS)); - } else if (TAG_PROTECTED_PACKAGES.equals(tag)) { - policy.mUserControlDisabledPackages.add( - parser.getAttributeValue(null, ATTR_NAME)); } else if (TAG_APPS_SUSPENDED.equals(tag)) { policy.mAppsSuspended = parser.getAttributeBoolean(null, ATTR_VALUE, false); } else if (TAG_BYPASS_ROLE_QUALIFICATIONS.equals(tag)) { policy.mBypassDevicePolicyManagementRoleQualifications = true; policy.mCurrentRoleHolder = parser.getAttributeValue(null, ATTR_VALUE); + // Deprecated tags below + } else if (TAG_PROTECTED_PACKAGES.equals(tag)) { + if (policy.mUserControlDisabledPackages == null) { + policy.mUserControlDisabledPackages = new ArrayList<>(); + } + policy.mUserControlDisabledPackages.add( + parser.getAttributeValue(null, ATTR_NAME)); } else { Slogf.w(TAG, "Unknown tag: %s", tag); XmlUtils.skipCurrentTag(parser); @@ -674,8 +674,6 @@ class DevicePolicyData { pw.increaseIndent(); pw.print("mPasswordOwner="); pw.println(mPasswordOwner); pw.print("mPasswordTokenHandle="); pw.println(Long.toHexString(mPasswordTokenHandle)); - pw.print("mUserControlDisabledPackages="); - pw.println(mUserControlDisabledPackages); pw.print("mAppsSuspended="); pw.println(mAppsSuspended); pw.print("mUserSetupComplete="); pw.println(mUserSetupComplete); pw.print("mAffiliationIds="); pw.println(mAffiliationIds); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7f466f38e96a..1ce24173d3a2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -536,7 +536,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to // be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade // step. - static final int DPMS_VERSION = 2; + static final int DPMS_VERSION = 3; static { SECURE_SETTINGS_ALLOWLIST = new ArraySet<>(); @@ -1908,39 +1908,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (userHandle == UserHandle.USER_SYSTEM) { mStateCache.setDeviceProvisioned(policy.mUserSetupComplete); } - - migrateDeviceOwnerProtectedPackagesToOwners(userHandle, policy); } return policy; } } /** - * Only used by {@link #getUserData(int)} to migrate <b>existing</b> device owner protected - * packages that were stored in {@link DevicePolicyData#mUserControlDisabledPackages} to - * {@link Owners} because the device owner protected packages are now stored on a per device - * owner basis instead of on a per user basis. - * - * Any calls to {@link #setUserControlDisabledPackages(ComponentName, List)} would now store - * the device owner protected packages in {@link Owners} instead of {@link DevicePolicyData}. - * @param userHandle The device owner user - * @param policy The policy data of the device owner user - */ - private void migrateDeviceOwnerProtectedPackagesToOwners( - int userHandle, DevicePolicyData policy) { - ComponentName deviceOwnerComponent = getOwnerComponent(userHandle); - if (isDeviceOwner(deviceOwnerComponent, userHandle) - && !policy.mUserControlDisabledPackages.isEmpty()) { - mOwners.setDeviceOwnerProtectedPackages( - deviceOwnerComponent.getPackageName(), - policy.mUserControlDisabledPackages); - - policy.mUserControlDisabledPackages = new ArrayList<>(); - saveSettingsLocked(userHandle); - } - } - - /** * Creates and loads the policy data from xml for data that is shared between * various profiles of a user. In contrast to {@link #getUserData(int)} * it allows access to data of users other than the calling user. @@ -3143,6 +3116,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { deleteTransferOwnershipBundleLocked(metadata.userId); } updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true); + pushUserControlDisabledPackagesLocked(metadata.userId); } private void maybeLogStart() { @@ -3182,6 +3156,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void handleStartUser(int userId) { synchronized (getLockObject()) { pushScreenCapturePolicy(userId); + pushUserControlDisabledPackagesLocked(userId); } pushUserRestrictions(userId); // When system user is started (device boot), load cache for all users. @@ -3204,6 +3179,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { startOwnerService(userId, "start-user"); } + void pushUserControlDisabledPackagesLocked(int userId) { + final int targetUserId; + final ActiveAdmin owner; + if (getDeviceOwnerUserIdUncheckedLocked() == userId) { + owner = getDeviceOwnerAdminLocked(); + targetUserId = UserHandle.USER_ALL; + } else { + owner = getProfileOwnerAdminLocked(userId); + targetUserId = userId; + } + + List<String> protectedPackages = (owner == null || owner.protectedPackages == null) + ? Collections.emptyList() : owner.protectedPackages; + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.getPackageManagerInternal().setOwnerProtectedPackages( + targetUserId, protectedPackages)); + } + @Override void handleUnlockUser(int userId) { startOwnerService(userId, "unlock-user"); @@ -8802,6 +8795,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { setNetworkLoggingActiveInternal(false); deleteTransferOwnershipBundleLocked(userId); toggleBackupServiceActive(UserHandle.USER_SYSTEM, true); + pushUserControlDisabledPackagesLocked(userId); } private void clearApplicationRestrictions(int userId) { @@ -8986,7 +8980,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mLockTaskPackages.clear(); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId); policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE; - policy.mUserControlDisabledPackages.clear(); saveSettingsLocked(userId); try { @@ -16979,19 +16972,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(packages, "packages is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller) + || isFinancedDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe( DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES); synchronized (getLockObject()) { - mOwners.setDeviceOwnerProtectedPackages(who.getPackageName(), packages); - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES) - .setAdmin(who) - .setStrings(packages.toArray(new String[packages.size()])) - .write(); + ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId()); + if (!Objects.equals(owner.protectedPackages, packages)) { + owner.protectedPackages = packages.isEmpty() ? null : packages; + saveSettingsLocked(caller.getUserId()); + pushUserControlDisabledPackagesLocked(caller.getUserId()); + } } + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES) + .setAdmin(who) + .setStrings(packages.toArray(new String[packages.size()])) + .write(); } @Override @@ -16999,11 +16998,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller) + || isFinancedDeviceOwner(caller)); synchronized (getLockObject()) { - return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName()); + ActiveAdmin deviceOwner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId()); + return deviceOwner.protectedPackages != null + ? deviceOwner.protectedPackages : Collections.emptyList(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index d1c6b3411b20..08bd3e4c7c47 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -31,7 +31,6 @@ import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; @@ -49,7 +48,6 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.File; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -108,12 +106,6 @@ class Owners { notifyChangeLocked(); pushToActivityTaskManagerLocked(); - - for (ArrayMap.Entry<String, List<String>> entry : - mData.mDeviceOwnerProtectedPackages.entrySet()) { - mPackageManagerInternal.setDeviceOwnerProtectedPackages( - entry.getKey(), entry.getValue()); - } } } @@ -247,12 +239,6 @@ class Owners { void clearDeviceOwner() { synchronized (mData) { mData.mDeviceOwnerTypes.remove(mData.mDeviceOwner.packageName); - List<String> protectedPackages = - mData.mDeviceOwnerProtectedPackages.remove(mData.mDeviceOwner.packageName); - if (protectedPackages != null) { - mPackageManagerInternal.setDeviceOwnerProtectedPackages( - mData.mDeviceOwner.packageName, new ArrayList<>()); - } mData.mDeviceOwner = null; mData.mDeviceOwnerUserId = UserHandle.USER_NULL; @@ -296,12 +282,6 @@ class Owners { synchronized (mData) { Integer previousDeviceOwnerType = mData.mDeviceOwnerTypes.remove( mData.mDeviceOwner.packageName); - List<String> previousProtectedPackages = - mData.mDeviceOwnerProtectedPackages.remove(mData.mDeviceOwner.packageName); - if (previousProtectedPackages != null) { - mPackageManagerInternal.setDeviceOwnerProtectedPackages( - mData.mDeviceOwner.packageName, new ArrayList<>()); - } // We don't set a name because it's not used anyway. // See DevicePolicyManagerService#getDeviceOwnerName mData.mDeviceOwner = new OwnerInfo(null, target, @@ -313,10 +293,6 @@ class Owners { mData.mDeviceOwnerTypes.put( mData.mDeviceOwner.packageName, previousDeviceOwnerType); } - if (previousProtectedPackages != null) { - mData.mDeviceOwnerProtectedPackages.put( - mData.mDeviceOwner.packageName, previousProtectedPackages); - } notifyChangeLocked(); pushToActivityTaskManagerLocked(); } @@ -504,34 +480,6 @@ class Owners { } } - void setDeviceOwnerProtectedPackages(String packageName, List<String> protectedPackages) { - synchronized (mData) { - if (!hasDeviceOwner()) { - Slog.e(TAG, - "Attempting to set device owner protected packages when there is no " - + "device owner"); - return; - } else if (!mData.mDeviceOwner.packageName.equals(packageName)) { - Slog.e(TAG, "Attempting to set device owner protected packages when the provided " - + "package name " + packageName - + " does not match the device owner package name"); - return; - } - - mData.mDeviceOwnerProtectedPackages.put(packageName, protectedPackages); - mPackageManagerInternal.setDeviceOwnerProtectedPackages(packageName, protectedPackages); - writeDeviceOwner(); - } - } - - List<String> getDeviceOwnerProtectedPackages(String packageName) { - synchronized (mData) { - return mData.mDeviceOwnerProtectedPackages.containsKey(packageName) - ? mData.mDeviceOwnerProtectedPackages.get(packageName) - : Collections.emptyList(); - } - } - void writeDeviceOwner() { synchronized (mData) { pushToDevicePolicyManager(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 4fe4f0d83f1a..694842034d1f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -91,8 +91,10 @@ class OwnersData { // Device owner type for a managed device. final ArrayMap<String, Integer> mDeviceOwnerTypes = new ArrayMap<>(); - final ArrayMap<String, List<String>> mDeviceOwnerProtectedPackages = new ArrayMap<>(); - + /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */ + @Deprecated + @Nullable + ArrayMap<String, List<String>> mDeviceOwnerProtectedPackages; // Internal state for the profile owner packages. final ArrayMap<Integer, OwnerInfo> mProfileOwners = new ArrayMap<>(); @@ -366,21 +368,6 @@ class OwnersData { } } - if (!mDeviceOwnerProtectedPackages.isEmpty()) { - for (ArrayMap.Entry<String, List<String>> entry : - mDeviceOwnerProtectedPackages.entrySet()) { - List<String> protectedPackages = entry.getValue(); - - out.startTag(null, TAG_DEVICE_OWNER_PROTECTED_PACKAGES); - out.attribute(null, ATTR_PACKAGE, entry.getKey()); - out.attributeInt(null, ATTR_SIZE, protectedPackages.size()); - for (int i = 0, size = protectedPackages.size(); i < size; i++) { - out.attribute(null, ATTR_NAME + i, protectedPackages.get(i)); - } - out.endTag(null, TAG_DEVICE_OWNER_PROTECTED_PACKAGES); - } - } - if (mSystemUpdatePolicy != null) { out.startTag(null, TAG_SYSTEM_UPDATE_POLICY); mSystemUpdatePolicy.saveToXml(out); @@ -444,6 +431,7 @@ class OwnersData { null, ATTR_DEVICE_OWNER_TYPE_VALUE, DEVICE_OWNER_TYPE_DEFAULT); mDeviceOwnerTypes.put(packageName, deviceOwnerType); break; + // Deprecated fields below. case TAG_DEVICE_OWNER_PROTECTED_PACKAGES: packageName = parser.getAttributeValue(null, ATTR_PACKAGE); int protectedPackagesSize = parser.getAttributeInt(null, ATTR_SIZE, 0); @@ -451,6 +439,9 @@ class OwnersData { for (int i = 0; i < protectedPackagesSize; i++) { protectedPackages.add(parser.getAttributeValue(null, ATTR_NAME + i)); } + if (mDeviceOwnerProtectedPackages == null) { + mDeviceOwnerProtectedPackages = new ArrayMap<>(); + } mDeviceOwnerProtectedPackages.put(packageName, protectedPackages); break; default: diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java index 7556d690f6fd..253851cdbf68 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy; +import android.annotation.Nullable; import android.content.ComponentName; import android.os.UserHandle; import android.util.Slog; @@ -28,6 +29,8 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; /** * Class for dealing with Device Policy Manager Service version upgrades. @@ -91,30 +94,82 @@ public class PolicyVersionUpgrader { if (currentVersion == 1) { Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion)); - // This upgrade step is for Device Owner scenario only: For devices upgrading to S, - // if there is a device owner, it retains the ability to control sensors-related - // permission grants. - for (int userId : allUsers) { - DevicePolicyData userData = allUsersData.get(userId); - if (userData == null) { - continue; - } - for (ActiveAdmin admin : userData.mAdminList) { - if (ownersData.mDeviceOwnerUserId == userId - && ownersData.mDeviceOwner != null - && ownersData.mDeviceOwner.admin.equals(admin.info.getComponent())) { - Slog.i(LOG_TAG, String.format( - "Marking Device Owner in user %d for permission grant ", userId)); - admin.mAdminCanGrantSensorsPermissions = true; - } - } - } + upgradeSensorPermissionsAccess(allUsers, ownersData, allUsersData); currentVersion = 2; } + if (currentVersion == 2) { + Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion)); + upgradeProtectedPackages(ownersData, allUsersData); + currentVersion = 3; + } + writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion); } + /** + * This upgrade step is for Device Owner scenario only: For devices upgrading to S, if there is + * a device owner, it retains the ability to control sensors-related permission grants. + */ + private void upgradeSensorPermissionsAccess( + int[] allUsers, OwnersData ownersData, SparseArray<DevicePolicyData> allUsersData) { + for (int userId : allUsers) { + DevicePolicyData userData = allUsersData.get(userId); + if (userData == null) { + continue; + } + for (ActiveAdmin admin : userData.mAdminList) { + if (ownersData.mDeviceOwnerUserId == userId + && ownersData.mDeviceOwner != null + && ownersData.mDeviceOwner.admin.equals(admin.info.getComponent())) { + Slog.i(LOG_TAG, String.format( + "Marking Device Owner in user %d for permission grant ", userId)); + admin.mAdminCanGrantSensorsPermissions = true; + } + } + } + } + + /** + * This upgrade step moves device owner protected packages to ActiveAdmin. + * Initially these packages were stored in DevicePolicyData, then moved to Owners without + * employing PolicyVersionUpgrader. Here we check both places. + */ + private void upgradeProtectedPackages( + OwnersData ownersData, SparseArray<DevicePolicyData> allUsersData) { + if (ownersData.mDeviceOwner == null) { + return; + } + List<String> protectedPackages = null; + DevicePolicyData doUserData = allUsersData.get(ownersData.mDeviceOwnerUserId); + if (doUserData == null) { + Slog.e(LOG_TAG, "No policy data for do user"); + return; + } + if (ownersData.mDeviceOwnerProtectedPackages != null) { + protectedPackages = ownersData.mDeviceOwnerProtectedPackages + .get(ownersData.mDeviceOwner.packageName); + if (protectedPackages != null) { + Slog.i(LOG_TAG, "Found protected packages in Owners"); + } + ownersData.mDeviceOwnerProtectedPackages = null; + } else if (doUserData.mUserControlDisabledPackages != null) { + Slog.i(LOG_TAG, "Found protected packages in DevicePolicyData"); + protectedPackages = doUserData.mUserControlDisabledPackages; + doUserData.mUserControlDisabledPackages = null; + } + + ActiveAdmin doAdmin = doUserData.mAdminMap.get(ownersData.mDeviceOwner.admin); + if (doAdmin == null) { + Slog.e(LOG_TAG, "DO admin not found in DO user"); + return; + } + + if (protectedPackages != null) { + doAdmin.protectedPackages = new ArrayList<>(protectedPackages); + } + } + private OwnersData loadOwners(int[] allUsers) { OwnersData ownersData = new OwnersData(mPathProvider); ownersData.load(allUsers); @@ -146,17 +201,23 @@ public class PolicyVersionUpgrader { OwnersData ownersData) { final SparseArray<DevicePolicyData> allUsersData = new SparseArray<>(); for (int user: allUsers) { - ComponentName owner = null; - if (ownersData.mDeviceOwnerUserId == user && ownersData.mDeviceOwner != null) { - owner = ownersData.mDeviceOwner.admin; - } else if (ownersData.mProfileOwners.containsKey(user)) { - owner = ownersData.mProfileOwners.get(user).admin; - } + ComponentName owner = getOwnerForUser(ownersData, user); allUsersData.append(user, loadDataForUser(user, loadVersion, owner)); } return allUsersData; } + @Nullable + private ComponentName getOwnerForUser(OwnersData ownersData, int user) { + ComponentName owner = null; + if (ownersData.mDeviceOwnerUserId == user && ownersData.mDeviceOwner != null) { + owner = ownersData.mDeviceOwner.admin; + } else if (ownersData.mProfileOwners.containsKey(user)) { + owner = ownersData.mProfileOwners.get(user).admin; + } + return owner; + } + private DevicePolicyData loadDataForUser( int userId, int loadVersion, ComponentName ownerComponent) { DevicePolicyData policy = new DevicePolicyData(userId); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dc2b6f8d3b64..80f0186a2528 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -338,8 +338,6 @@ public final class SystemServer implements Dumpable { "com.android.server.contentcapture.ContentCaptureManagerService"; private static final String TRANSLATION_MANAGER_SERVICE_CLASS = "com.android.server.translation.TranslationManagerService"; - private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS = - "com.android.server.selectiontoolbar.SelectionToolbarManagerService"; private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS = "com.android.server.musicrecognition.MusicRecognitionManagerService"; private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS = @@ -2635,11 +2633,6 @@ public final class SystemServer implements Dumpable { Slog.d(TAG, "TranslationService not defined by OEM"); } - // Selection toolbar service - t.traceBegin("StartSelectionToolbarManagerService"); - mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS); - t.traceEnd(); - // NOTE: ClipboardService depends on ContentCapture and Autofill t.traceBegin("StartClipboardService"); mSystemServiceManager.startService(ClipboardService.class); diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp deleted file mode 100644 index cc6405f97bc3..000000000000 --- a/services/selectiontoolbar/Android.bp +++ /dev/null @@ -1,22 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -filegroup { - name: "services.selectiontoolbar-sources", - srcs: ["java/**/*.java"], - path: "java", - visibility: ["//frameworks/base/services"], -} - -java_library_static { - name: "services.selectiontoolbar", - defaults: ["platform_service_defaults"], - srcs: [":services.selectiontoolbar-sources"], - libs: ["services.core"], -} diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 8461b39f8899..17b42260948d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -3220,34 +3220,19 @@ public class AlarmManagerServiceTest { when(mRoleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_WELLBEING)).thenReturn( Arrays.asList(package4)); - mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); - mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = false; - mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] { - package1, - package3, - }); - - // Deny listed packages will be false. - assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1)); - assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2)); - assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3)); - assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4)); - mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false); - mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true; mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] { package1, package3, }); - // Same as above, deny listed packages will be false. + // Deny listed packages will be false. assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package1, uid1)); assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package2, uid2)); assertFalse(mService.isScheduleExactAlarmAllowedByDefault(package3, uid3)); assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4)); mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); - mService.mConstants.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true; mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] { package1, package3, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index ac542935fa0a..009dae51e94b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -582,6 +582,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null; DeviceConfigSession<Long> bgCurrentDrainWindow = null; + DeviceConfigSession<Long> bgCurrentDrainInteractionGracePeriod = null; DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null; DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null; DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null; @@ -617,6 +618,14 @@ public final class BackgroundRestrictionTest { R.integer.config_bg_current_drain_window)); bgCurrentDrainWindow.set(windowMs); + bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD, + DeviceConfig::getLong, + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_window)); + bgCurrentDrainInteractionGracePeriod.set(windowMs); + bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, @@ -768,6 +777,32 @@ public final class BackgroundRestrictionTest { clearInvocations(mInjector.getAppStandbyInternal()); + // It won't be restricted since user just interacted with it. + runTestBgCurrentDrainMonitorOnce(listener, stats, uids, + zeros, new double[]{0, restrictBucketThresholdMah - 1}, + zeros, new double[]{restrictBucketThresholdMah + 1, 0}, + () -> { + doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); + doReturn(mCurrentTimeMillis + windowMs) + .when(stats).getStatsEndTimestamp(); + mCurrentTimeMillis += windowMs + 1; + try { + listener.verify(timeout, testUid, testPkgName, + RESTRICTION_LEVEL_RESTRICTED_BUCKET); + fail("There shouldn't be any level change events"); + } catch (Exception e) { + // Expected. + } + verify(mInjector.getAppStandbyInternal(), never()).restrictApp( + eq(testPkgName), + eq(testUser), + anyInt(), anyInt()); + }); + + // Sleep a while. + Thread.sleep(windowMs); + clearInvocations(mInjector.getAppStandbyInternal()); + // Now it should have been restricted. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, zeros, new double[]{0, restrictBucketThresholdMah - 1}, zeros, new double[]{restrictBucketThresholdMah + 1, 0}, @@ -1061,6 +1096,7 @@ public final class BackgroundRestrictionTest { } finally { closeIfNotNull(bgCurrentDrainMonitor); closeIfNotNull(bgCurrentDrainWindow); + closeIfNotNull(bgCurrentDrainInteractionGracePeriod); closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold); closeIfNotNull(bgCurrentDrainBgRestrictedThreshold); closeIfNotNull(bgPromptFgsWithNotiToBgRestricted); @@ -1610,6 +1646,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null; DeviceConfigSession<Long> bgCurrentDrainWindow = null; + DeviceConfigSession<Long> bgCurrentDrainInteractionGracePeriod = null; DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null; DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null; DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketHighThreshold = null; @@ -1655,6 +1692,14 @@ public final class BackgroundRestrictionTest { R.integer.config_bg_current_drain_window)); bgCurrentDrainWindow.set(windowMs); + bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD, + DeviceConfig::getLong, + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_window)); + bgCurrentDrainInteractionGracePeriod.set(windowMs); + bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, @@ -2176,6 +2221,7 @@ public final class BackgroundRestrictionTest { } finally { closeIfNotNull(bgCurrentDrainMonitor); closeIfNotNull(bgCurrentDrainWindow); + closeIfNotNull(bgCurrentDrainInteractionGracePeriod); closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold); closeIfNotNull(bgCurrentDrainBgRestrictedThreshold); closeIfNotNull(bgCurrentDrainRestrictedBucketHighThreshold); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java index 444db9128662..da5c8f06bc86 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java @@ -16,6 +16,9 @@ package com.android.server.pm; +import static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED; +import static com.android.server.pm.BackgroundDexOptService.STATUS_OK; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -23,12 +26,14 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.annotation.Nullable; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -38,10 +43,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.HandlerThread; import android.os.PowerManager; +import android.util.Log; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.PinnerService; import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.DexoptOptions; import org.junit.After; import org.junit.Before; @@ -52,7 +60,11 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; @@ -66,8 +78,12 @@ public final class BackgroundDexOptServiceUnitTest { private static final long TEST_WAIT_TIMEOUT_MS = 10_000; - private static final List<String> DEFAULT_PACKAGE_LIST = List.of("aaa", "bbb"); - private static final List<String> EMPTY_PACKAGE_LIST = List.of(); + private static final String PACKAGE_AAA = "aaa"; + private static final List<String> DEFAULT_PACKAGE_LIST = List.of(PACKAGE_AAA, "bbb"); + private int mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED; + + // Store expected dexopt sequence for verification. + private ArrayList<DexOptInfo> mDexInfoSequence = new ArrayList<>(); @Mock private Context mContext; @@ -116,14 +132,23 @@ public final class BackgroundDexOptServiceUnitTest { when(mInjector.getDexOptThermalCutoff()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL); when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_NONE); when(mInjector.supportSecondaryDex()).thenReturn(true); - when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST); - when(mDexOptHelper.performDexOptWithStatus(any())).thenReturn( - PackageDexOptimizer.DEX_OPT_PERFORMED); - when(mDexOptHelper.performDexOpt(any())).thenReturn(true); + setupDexOptHelper(); mService = new BackgroundDexOptService(mInjector); } + private void setupDexOptHelper() { + when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST); + when(mDexOptHelper.performDexOptWithStatus(any())).thenAnswer(inv -> { + DexoptOptions opt = inv.getArgument(0); + if (opt.getPackageName().equals(PACKAGE_AAA)) { + return mDexOptResultForPackageAAA; + } + return PackageDexOptimizer.DEX_OPT_PERFORMED; + }); + when(mDexOptHelper.performDexOpt(any())).thenReturn(true); + } + @After public void tearDown() throws Exception { LocalServices.removeServiceForTest(BackgroundDexOptService.class); @@ -159,7 +184,7 @@ public final class BackgroundDexOptServiceUnitTest { @Test public void testNoExecutionForNoOptimizablePackages() { initUntilBootCompleted(); - when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(EMPTY_PACKAGE_LIST); + when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(Collections.emptyList()); assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isFalse(); @@ -170,15 +195,70 @@ public final class BackgroundDexOptServiceUnitTest { public void testPostBootUpdateFullRun() { initUntilBootCompleted(); - runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, false, 1); + runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, + /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); + } + + @Test + public void testPostBootUpdateFullRunWithPackageFailure() { + mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED; + + initUntilBootCompleted(); + + runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED, + /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); + + assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); + assertThat(getFailedPackageNamesSecondary()).isEmpty(); } @Test public void testIdleJobFullRun() { initUntilBootCompleted(); + runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, + /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); + runFullJob(mJobServiceForIdle, mJobParametersForIdle, + /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK, + /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); + } + + @Test + public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() { + mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED; + + initUntilBootCompleted(); + + runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED, + /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); + + assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); + assertThat(getFailedPackageNamesSecondary()).isEmpty(); + + runFullJob(mJobServiceForIdle, mJobParametersForIdle, + /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK, + /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); + + assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); + assertThat(getFailedPackageNamesSecondary()).isEmpty(); + + mService.notifyPackageChanged(PACKAGE_AAA); - runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, false, 1); - runFullJob(mJobServiceForIdle, mJobParametersForIdle, true, 2); + assertThat(getFailedPackageNamesPrimary()).isEmpty(); + assertThat(getFailedPackageNamesSecondary()).isEmpty(); + + // Succeed this time. + mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED; + + runFullJob(mJobServiceForIdle, mJobParametersForIdle, + /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK, + /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null); + + assertThat(getFailedPackageNamesPrimary()).isEmpty(); + assertThat(getFailedPackageNamesSecondary()).isEmpty(); } @Test @@ -404,8 +484,10 @@ public final class BackgroundDexOptServiceUnitTest { } private void runFullJob(BackgroundDexOptJobService jobService, JobParameters params, - boolean expectedReschedule, int totalJobRuns) { + boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams, + @Nullable String expectedSkippedPackage) { when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread()); + addFullRunSequence(expectedSkippedPackage); assertThat(mService.onStartJob(jobService, params)).isTrue(); ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class); @@ -413,20 +495,99 @@ public final class BackgroundDexOptServiceUnitTest { argThreadRunnable.getValue().run(); - verify(jobService).jobFinished(params, expectedReschedule); + verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params, + expectedReschedule); // Never block verify(mDexOptHelper, never()).controlDexOptBlocking(true); - verifyPerformDexOpt(DEFAULT_PACKAGE_LIST, totalJobRuns); + verifyPerformDexOpt(); + assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus); } - private void verifyPerformDexOpt(List<String> pkgs, int expectedRuns) { + private void verifyPerformDexOpt() { InOrder inOrder = inOrder(mDexOptHelper); - for (int i = 0; i < expectedRuns; i++) { - for (String pkg : pkgs) { - inOrder.verify(mDexOptHelper, times(1)).performDexOptWithStatus(argThat((option) -> - option.getPackageName().equals(pkg) && !option.isDexoptOnlySecondaryDex())); - inOrder.verify(mDexOptHelper, times(1)).performDexOpt(argThat((option) -> - option.getPackageName().equals(pkg) && option.isDexoptOnlySecondaryDex())); + inOrder.verify(mDexOptHelper).getOptimizablePackages(any()); + for (DexOptInfo info : mDexInfoSequence) { + if (info.isPrimary) { + verify(mDexOptHelper).performDexOptWithStatus( + argThat((option) -> option.getPackageName().equals(info.packageName) + && !option.isDexoptOnlySecondaryDex())); + } else { + inOrder.verify(mDexOptHelper).performDexOpt( + argThat((option) -> option.getPackageName().equals(info.packageName) + && option.isDexoptOnlySecondaryDex())); + } + } + + // Even InOrder cannot check the order if the same call is made multiple times. + // To check the order across multiple runs, we reset the mock so that order can be checked + // in each call. + mDexInfoSequence.clear(); + reset(mDexOptHelper); + setupDexOptHelper(); + } + + private String findDumpValueForKey(String key) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintWriter pw = new PrintWriter(out, true); + IndentingPrintWriter writer = new IndentingPrintWriter(pw, ""); + try { + mService.dump(writer); + writer.flush(); + Log.i(TAG, "dump output:" + out.toString()); + for (String line : out.toString().split(System.lineSeparator())) { + String[] vals = line.split(":"); + if (vals[0].equals(key)) { + if (vals.length == 2) { + return vals[1].strip(); + } else { + break; + } + } + } + return ""; + } finally { + writer.close(); + } + } + + List<String> findStringListFromDump(String key) { + String values = findDumpValueForKey(key); + if (values.isEmpty()) { + return Collections.emptyList(); + } + return Arrays.asList(values.split(",")); + } + + private List<String> getFailedPackageNamesPrimary() { + return findStringListFromDump("mFailedPackageNamesPrimary"); + } + + private List<String> getFailedPackageNamesSecondary() { + return findStringListFromDump("mFailedPackageNamesSecondary"); + } + + private int getLastExecutionStatus() { + return Integer.parseInt(findDumpValueForKey("mLastExecutionStatus")); + } + + private static class DexOptInfo { + public final String packageName; + public final boolean isPrimary; + + private DexOptInfo(String packageName, boolean isPrimary) { + this.packageName = packageName; + this.isPrimary = isPrimary; + } + } + + private void addFullRunSequence(@Nullable String expectedSkippedPackage) { + for (String packageName : DEFAULT_PACKAGE_LIST) { + if (packageName.equals(expectedSkippedPackage)) { + // only fails primary dexopt in mocking but add secodary + mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false)); + } else { + mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ true)); + mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false)); } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java index fa3e05a6b001..20cfd59973c3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java @@ -24,20 +24,33 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import android.app.AppOpsManager; import android.content.Context; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.hardware.lights.Light; +import android.hardware.lights.LightState; import android.hardware.lights.LightsManager; import android.hardware.lights.LightsRequest; +import android.os.Handler; +import android.os.Looper; import android.permission.PermissionManager; import android.util.ArraySet; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.R; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -53,26 +66,43 @@ import java.util.stream.Collectors; public class CameraPrivacyLightControllerTest { + private int mDayColor = 1; + private int mNightColor = 0; + private int mCameraPrivacyLightAlsAveragingIntervalMillis = 5000; + private int mCameraPrivacyLightAlsNightThreshold = (int) getLightSensorValue(15); + private MockitoSession mMockitoSession; @Mock private Context mContext; @Mock + private Resources mResources; + + @Mock private LightsManager mLightsManager; @Mock private AppOpsManager mAppOpsManager; @Mock + private SensorManager mSensorManager; + + @Mock private LightsManager.LightsSession mLightsSession; + @Mock + private Sensor mLightSensor; + private ArgumentCaptor<AppOpsManager.OnOpActiveChangedListener> mAppOpsListenerCaptor = ArgumentCaptor.forClass(AppOpsManager.OnOpActiveChangedListener.class); private ArgumentCaptor<LightsRequest> mLightsRequestCaptor = ArgumentCaptor.forClass(LightsRequest.class); + private ArgumentCaptor<SensorEventListener> mLightSensorListenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + private Set<String> mExemptedPackages = new ArraySet<>(); private List<Light> mLights = new ArrayList<>(); @@ -86,11 +116,22 @@ public class CameraPrivacyLightControllerTest { .spyStatic(PermissionManager.class) .startMocking(); + doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day); + doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night); + + doReturn(mResources).when(mContext).getResources(); + doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources) + .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis); + doReturn(mCameraPrivacyLightAlsNightThreshold).when(mResources) + .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold); + doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class); doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); + doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class); doReturn(mLights).when(mLightsManager).getLights(); doReturn(mLightsSession).when(mLightsManager).openSession(anyInt()); + doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); doReturn(mExemptedPackages) .when(() -> PermissionManager.getIndicatorExemptedPackages(any())); @@ -107,7 +148,7 @@ public class CameraPrivacyLightControllerTest { @Test public void testAppsOpsListenerNotRegisteredWithoutCameraLights() { mLights.add(getNextLight(false)); - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any()); } @@ -116,7 +157,7 @@ public class CameraPrivacyLightControllerTest { public void testAppsOpsListenerRegisteredWithCameraLight() { mLights.add(getNextLight(true)); - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any()); } @@ -128,14 +169,13 @@ public class CameraPrivacyLightControllerTest { mLights.add(getNextLight(r.nextBoolean())); } - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); // Verify no session has been opened at this point. verify(mLightsManager, times(0)).openSession(anyInt()); // Set camera op as active. - verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); - mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + openCamera(); // Verify session has been opened exactly once verify(mLightsManager, times(1)).openSession(anyInt()); @@ -161,7 +201,7 @@ public class CameraPrivacyLightControllerTest { public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() { mLights.add(getNextLight(true)); - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); @@ -176,7 +216,7 @@ public class CameraPrivacyLightControllerTest { public void testWillCloseOnFinishOp() { mLights.add(getNextLight(true)); - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); @@ -192,7 +232,7 @@ public class CameraPrivacyLightControllerTest { public void testWillCloseOnFinishOpForAllPackages() { mLights.add(getNextLight(true)); - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); int numUids = 100; List<Integer> uids = new ArrayList<>(numUids); @@ -226,7 +266,7 @@ public class CameraPrivacyLightControllerTest { mLights.add(getNextLight(true)); mExemptedPackages.add("pkg1"); - new CameraPrivacyLightController(mContext); + createCameraPrivacyLightController(); verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); @@ -235,6 +275,147 @@ public class CameraPrivacyLightControllerTest { verify(mLightsManager, times(0)).openSession(anyInt()); } + @Test + public void testNoLightSensor() { + mLights.add(getNextLight(true)); + doReturn(null).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); + + createCameraPrivacyLightController(); + + openCamera(); + + verify(mLightsSession).requestLights(mLightsRequestCaptor.capture()); + LightsRequest lightsRequest = mLightsRequestCaptor.getValue(); + for (LightState lightState : lightsRequest.getLightStates()) { + assertEquals(mDayColor, lightState.getColor()); + } + } + + @Test + public void testALSListenerNotRegisteredUntilCameraIsOpened() { + mLights.add(getNextLight(true)); + Sensor sensor = mock(Sensor.class); + doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); + + CameraPrivacyLightController cplc = createCameraPrivacyLightController(); + + verify(mSensorManager, never()).registerListener(any(SensorEventListener.class), + any(Sensor.class), anyInt(), any(Handler.class)); + + openCamera(); + + verify(mSensorManager, times(1)).registerListener(mLightSensorListenerCaptor.capture(), + any(Sensor.class), anyInt(), any(Handler.class)); + + mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", false); + verify(mSensorManager, times(1)).unregisterListener(mLightSensorListenerCaptor.getValue()); + } + + @Ignore + @Test + public void testDayColor() { + testBrightnessToColor(20, mDayColor); + } + + @Ignore + @Test + public void testNightColor() { + testBrightnessToColor(10, mNightColor); + } + + private void testBrightnessToColor(int brightnessValue, int color) { + mLights.add(getNextLight(true)); + Sensor sensor = mock(Sensor.class); + doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); + + CameraPrivacyLightController cplc = createCameraPrivacyLightController(); + cplc.setElapsedRealTime(0); + + openCamera(); + + verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(), + any(Sensor.class), anyInt(), any(Handler.class)); + SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue(); + float[] sensorEventValues = new float[1]; + SensorEvent sensorEvent = new SensorEvent(sensor, 0, 0, sensorEventValues); + + sensorEventValues[0] = getLightSensorValue(brightnessValue); + sensorListener.onSensorChanged(sensorEvent); + + verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture()); + for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { + assertEquals(color, lightState.getColor()); + } + } + + @Ignore + @Test + public void testDayToNightTransistion() { + mLights.add(getNextLight(true)); + Sensor sensor = mock(Sensor.class); + doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT); + + CameraPrivacyLightController cplc = createCameraPrivacyLightController(); + cplc.setElapsedRealTime(0); + + openCamera(); + // There will be an initial call at brightness 0 + verify(mLightsSession, times(1)).requestLights(any(LightsRequest.class)); + + verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(), + any(Sensor.class), anyInt(), any(Handler.class)); + SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue(); + + onSensorEvent(cplc, sensorListener, sensor, 0, 20); + + // 5 sec avg = 20 + onSensorEvent(cplc, sensorListener, sensor, 5000, 30); + + verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture()); + for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { + assertEquals(mDayColor, lightState.getColor()); + } + + // 5 sec avg = 22 + + onSensorEvent(cplc, sensorListener, sensor, 6000, 10); + + // 5 sec avg = 18 + + onSensorEvent(cplc, sensorListener, sensor, 8000, 5); + + // Should have always been day + verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture()); + for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { + assertEquals(mDayColor, lightState.getColor()); + } + + // 5 sec avg = 12 + + onSensorEvent(cplc, sensorListener, sensor, 10000, 5); + + // Should now be night + verify(mLightsSession, times(3)).requestLights(mLightsRequestCaptor.capture()); + for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) { + assertEquals(mNightColor, lightState.getColor()); + } + } + + private void onSensorEvent(CameraPrivacyLightController cplc, + SensorEventListener sensorListener, Sensor sensor, long timestamp, int value) { + cplc.setElapsedRealTime(timestamp); + sensorListener.onSensorChanged(new SensorEvent(sensor, 0, timestamp, + new float[] {getLightSensorValue(value)})); + } + + // Use the test thread so that the test is deterministic + private CameraPrivacyLightController createCameraPrivacyLightController() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + return new CameraPrivacyLightController(mContext, Looper.myLooper()); + } + private Light getNextLight(boolean cameraType) { Light light = ExtendedMockito.mock(Light.class); if (cameraType) { @@ -245,4 +426,13 @@ public class CameraPrivacyLightControllerTest { doReturn(mNextLightId++).when(light).getId(); return light; } + + private float getLightSensorValue(int i) { + return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER); + } + + private void openCamera() { + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true); + } } diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_owner_2.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_owner_2.xml new file mode 100644 index 000000000000..0725d25cdcab --- /dev/null +++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_owner_2.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<root>
+ <device-owner package="com.android.frameworks.servicestests" name="" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" userRestrictionsMigrated="true" isPoOrganizationOwnedDevice="true" />
+ <device-owner-context userId="0" />
+ <device-owner-protected-packages package="com.android.frameworks.servicestests" size="2" name0="com.some.app" name1="foo.bar.baz" />
+</root>
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_policies.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_policies.xml new file mode 100644 index 000000000000..2d06ee6dca0e --- /dev/null +++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/protected_packages_device_policies.xml @@ -0,0 +1,13 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + <policies flags="991" /> + <strong-auth-unlock-timeout value="0" /> + <test-only-admin value="true" /> + <cross-profile-calendar-packages /> + <cross-profile-packages /> + </admin> + <lock-task-features value="16" /> + <protected-packages name="com.some.app" /> + <protected-packages name="foo.bar.baz" /> +</policies> diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java index 18e0f29d4166..bce99a09c6d2 100644 --- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -416,6 +416,7 @@ public class BroadcastRecordTest { null /* resolvedType */, null /* requiredPermissions */, null /* excludedPermissions */, + null /* excludedPackages */, 0 /* appOp */, null /* options */, new ArrayList<>(receivers), // Make a copy to not affect the original list. diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 5b3a1284069e..98f0603ca633 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -339,7 +339,7 @@ public final class AppHibernationServiceTest { ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mIActivityManager, times(2)).broadcastIntentWithFeature(any(), any(), intentArgumentCaptor.capture(), any(), any(), anyInt(), any(), any(), any(), any(), - anyInt(), any(), anyBoolean(), anyBoolean(), eq(USER_ID_1)); + any(), anyInt(), any(), anyBoolean(), anyBoolean(), eq(USER_ID_1)); List<Intent> capturedIntents = intentArgumentCaptor.getAllValues(); assertEquals(capturedIntents.get(0).getAction(), Intent.ACTION_LOCKED_BOOT_COMPLETED); assertEquals(capturedIntents.get(1).getAction(), Intent.ACTION_BOOT_COMPLETED); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 0fc201eae3d4..ec6b67450a1a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -6996,22 +6996,23 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setUserControlDisabledPackages(admin1, testPackages); verify(getServices().packageManagerInternal) - .setDeviceOwnerProtectedPackages(admin1.getPackageName(), testPackages); + .setOwnerProtectedPackages(UserHandle.USER_ALL, testPackages); assertThat(dpm.getUserControlDisabledPackages(admin1)).isEqualTo(testPackages); } @Test - public void testSetUserControlDisabledPackages_failingAsPO() { + public void testSetUserControlDisabledPackages_asPO() { final List<String> testPackages = new ArrayList<>(); testPackages.add("package_1"); testPackages.add("package_2"); mServiceContext.permissions.add(permission.MANAGE_DEVICE_ADMINS); setAsProfileOwner(admin1); - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpm.setUserControlDisabledPackages(admin1, testPackages)); - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpm.getUserControlDisabledPackages(admin1)); + dpm.setUserControlDisabledPackages(admin1, testPackages); + + verify(getServices().packageManagerInternal) + .setOwnerProtectedPackages(CALLER_USER_HANDLE, testPackages); + assertThat(dpm.getUserControlDisabledPackages(admin1)).isEqualTo(testPackages); } private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) { @@ -7845,7 +7846,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setUserControlDisabledPackages(admin1, packages); verify(getServices().packageManagerInternal) - .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages)); + .setOwnerProtectedPackages(eq(UserHandle.USER_ALL), eq(packages)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java index 9efc10cb55ec..412722d68f43 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java @@ -63,8 +63,9 @@ import java.util.function.Function; public class PolicyVersionUpgraderTest extends DpmTestBase { // NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade // to the new version. - private static final int LATEST_TESTED_VERSION = 2; + private static final int LATEST_TESTED_VERSION = 3; public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions"; + public static final String DEVICE_OWNER_XML = "device_owner_2.xml"; private ComponentName mFakeAdmin; private class FakePolicyUpgraderDataProvider implements PolicyUpgraderDataProvider { @@ -119,18 +120,18 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { mUpgrader = new PolicyVersionUpgrader(mProvider, getServices().pathProvider); mFakeAdmin = new ComponentName( "com.android.frameworks.servicestests", - "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"); + "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"); ActivityInfo activityInfo = createActivityInfo(mFakeAdmin); DeviceAdminInfo dai = createDeviceAdminInfo(activityInfo); mProvider.mComponentToDeviceAdminInfo.put(mFakeAdmin, dai); - mProvider.mUsers = new int[] {0}; + mProvider.mUsers = new int[]{0}; } @Test public void testSameVersionDoesNothing() throws IOException { writeVersionToXml(DevicePolicyManagerService.DPMS_VERSION); final int userId = mProvider.mUsers[0]; - preparePoliciesFile(userId); + preparePoliciesFile(userId, "device_policies.xml"); String oldContents = readPoliciesFile(userId); mUpgrader.upgradePolicy(DevicePolicyManagerService.DPMS_VERSION); @@ -142,19 +143,19 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { @Test public void testUpgrade0To1RemovesPasswordMetrics() throws IOException, XmlPullParserException { final String activePasswordTag = "active-password"; - mProvider.mUsers = new int[] {0, 10}; + mProvider.mUsers = new int[]{0, 10}; getServices().addUser(10, /* flags= */ 0, USER_TYPE_PROFILE_MANAGED); writeVersionToXml(0); for (int userId : mProvider.mUsers) { - preparePoliciesFile(userId); + preparePoliciesFile(userId, "device_policies.xml"); } // Validate test set-up. assertThat(isTagPresent(readPoliciesFileToStream(0), activePasswordTag)).isTrue(); mUpgrader.upgradePolicy(1); - assertThat(readVersionFromXml()).isGreaterThan(1); - for (int user: mProvider.mUsers) { + assertThat(readVersionFromXml()).isAtLeast(1); + for (int user : mProvider.mUsers) { assertThat(isTagPresent(readPoliciesFileToStream(user), activePasswordTag)).isFalse(); } } @@ -163,17 +164,17 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { public void testUpgrade1To2MarksDoForPermissionControl() throws IOException, XmlPullParserException { final int ownerUser = 10; - mProvider.mUsers = new int[] {0, ownerUser}; + mProvider.mUsers = new int[]{0, ownerUser}; getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM); writeVersionToXml(1); for (int userId : mProvider.mUsers) { - preparePoliciesFile(userId); + preparePoliciesFile(userId, "device_policies.xml"); } - prepareDeviceOwnerFile(ownerUser); + prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml"); mUpgrader.upgradePolicy(2); - assertThat(readVersionFromXml()).isEqualTo(2); + assertThat(readVersionFromXml()).isAtLeast(2); assertThat(getBooleanValueTag(readPoliciesFileToStream(mProvider.mUsers[0]), PERMISSIONS_TAG)).isFalse(); assertThat(getBooleanValueTag(readPoliciesFileToStream(ownerUser), @@ -186,8 +187,8 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM); setUpPackageManagerForAdmin(admin1, UserHandle.getUid(ownerUser, 123 /* admin app ID */)); writeVersionToXml(0); - preparePoliciesFile(ownerUser); - prepareDeviceOwnerFile(ownerUser); + preparePoliciesFile(ownerUser, "device_policies.xml"); + prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml"); DevicePolicyManagerServiceTestable dpms; final long ident = getContext().binder.clearCallingIdentity(); @@ -202,11 +203,65 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { getContext().binder.restoreCallingIdentity(ident); } + assertThat(readVersionFromXml()).isEqualTo(DevicePolicyManagerService.DPMS_VERSION); + // DO should be marked as able to grant sensors permission during upgrade and should be // reported as such via the API. assertThat(dpms.canAdminGrantSensorsPermissionsForUser(ownerUser)).isTrue(); } + /** + * Up to Android R DO protected packages were stored in DevicePolicyData, verify that they are + * moved to ActiveAdmin. + */ + @Test + public void testUserControlDisabledPackagesFromR() throws Exception { + final String oldTag = "protected-packages"; + final String newTag = "protected_packages"; + final int ownerUser = 0; + mProvider.mUsers = new int[]{0}; + getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM); + writeVersionToXml(2); + preparePoliciesFile(ownerUser, "protected_packages_device_policies.xml"); + prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml"); + + // Validate the setup. + assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), oldTag)).isTrue(); + assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isFalse(); + + mUpgrader.upgradePolicy(3); + + assertThat(readVersionFromXml()).isAtLeast(3); + assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), oldTag)).isFalse(); + assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isTrue(); + } + + /** + * In Android S DO protected packages were stored in Owners, verify that they are moved to + * ActiveAdmin. + */ + @Test + public void testUserControlDisabledPackagesFromS() throws Exception { + final String oldTag = "device-owner-protected-packages"; + final String newTag = "protected_packages"; + final int ownerUser = 0; + mProvider.mUsers = new int[]{0}; + getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM); + writeVersionToXml(2); + preparePoliciesFile(ownerUser, "device_policies.xml"); + prepareDeviceOwnerFile(ownerUser, "protected_packages_device_owner_2.xml"); + + // Validate the setup. + assertThat(isTagPresent(readDoToStream(), oldTag)).isTrue(); + assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isFalse(); + + mUpgrader.upgradePolicy(3); + + assertThat(readVersionFromXml()).isAtLeast(3); + assertThat(isTagPresent(readDoToStream(), oldTag)).isFalse(); + assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isTrue(); + } + @Test public void isLatestVersionTested() { assertThat(DevicePolicyManagerService.DPMS_VERSION).isEqualTo(LATEST_TESTED_VERSION); @@ -226,32 +281,27 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { return Integer.parseInt(versionString); } - private void preparePoliciesFile(int userId) throws IOException { + private void preparePoliciesFile(int userId, String assetFile) throws IOException { JournaledFile policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId); DpmTestUtils.writeToFile( policiesFile.chooseForWrite(), - DpmTestUtils.readAsset(mRealTestContext, - "PolicyVersionUpgraderTest/device_policies.xml")); + DpmTestUtils.readAsset(mRealTestContext, "PolicyVersionUpgraderTest/" + assetFile)); policiesFile.commit(); } - private void prepareDeviceOwnerFile(int userId) throws IOException { - File parentDir = getServices().pathProvider.getDataSystemDirectory(); - File doFilePath = (new File(parentDir, "device_owner_2.xml")).getAbsoluteFile(); - android.util.Log.i("YYYYYY", "DO paath: " + doFilePath); + private void prepareDeviceOwnerFile(int userId, String assetFile) throws IOException { + File doFilePath = getDoFilePath(); String doFileContent = DpmTestUtils.readAsset(mRealTestContext, - "PolicyVersionUpgraderTest/device_owner_2.xml") + "PolicyVersionUpgraderTest/" + assetFile) // Substitute the right DO userId, XML in resources has 0 .replace("userId=\"0\"", "userId=\"" + userId + "\""); DpmTestUtils.writeToFile(doFilePath, doFileContent); } - private void prepareProfileOwnerFile(int userId) throws IOException { - File parentDir = getServices().pathProvider.getUserSystemDirectory(userId); - DpmTestUtils.writeToFile( - (new File(parentDir, "profile_owner.xml")).getAbsoluteFile(), - DpmTestUtils.readAsset(mRealTestContext, - "PolicyVersionUpgraderTest/profile_owner.xml")); + private File getDoFilePath() { + File parentDir = getServices().pathProvider.getDataSystemDirectory(); + File doFilePath = (new File(parentDir, DEVICE_OWNER_XML)).getAbsoluteFile(); + return doFilePath; } private String readPoliciesFile(int userId) throws IOException { @@ -259,6 +309,10 @@ public class PolicyVersionUpgraderTest extends DpmTestBase { return new String(Files.asByteSource(policiesFile).read(), Charset.defaultCharset()); } + private InputStream readDoToStream() throws IOException { + return new FileInputStream(getDoFilePath()); + } + private InputStream readPoliciesFileToStream(int userId) throws IOException { File policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId).chooseForRead(); return new FileInputStream(policiesFile); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java index e9515fa7fd9d..ffc0dcdca5fc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ImportanceExtractorTest.java @@ -82,8 +82,6 @@ public class ImportanceExtractorTest extends UiServiceTestCase { ImportanceExtractor extractor = new ImportanceExtractor(); extractor.setConfig(mConfig); - when(mConfig.getImportance(anyString(), anyInt())).thenReturn( - NotificationManager.IMPORTANCE_MIN); NotificationChannel channel = new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED); @@ -101,8 +99,6 @@ public class ImportanceExtractorTest extends UiServiceTestCase { ImportanceExtractor extractor = new ImportanceExtractor(); extractor.setConfig(mConfig); - when(mConfig.getImportance(anyString(), anyInt())).thenReturn( - NotificationManager.IMPORTANCE_MIN); NotificationChannel channel = new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java index f609306e44b0..6f7bacedc467 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java @@ -28,6 +28,13 @@ public class NotificationChannelLoggerFake implements NotificationChannelLogger CallRecord(NotificationChannelEvent event) { this.event = event; } + + @Override + public String toString() { + return "CallRecord{" + + "event=" + event + + '}'; + } } private List<CallRecord> mCalls = new ArrayList<>(); 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 c0cd7a755e25..a7dc8518daea 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -52,6 +52,7 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.PackageManager.FEATURE_TELECOM; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -187,6 +188,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.Pair; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -220,6 +222,7 @@ import com.android.server.wm.WindowManagerInternal; import com.google.common.collect.ImmutableList; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -272,6 +275,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private WindowManagerInternal mWindowManagerInternal; @Mock private PermissionHelper mPermissionHelper; + private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @@ -384,9 +388,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "android.permission.WRITE_DEVICE_CONFIG", "android.permission.READ_DEVICE_CONFIG", "android.permission.READ_CONTACTS"); - Settings.Secure.putIntForUser( - getContext().getContentResolver(), - Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM); MockitoAnnotations.initMocks(this); @@ -448,6 +449,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0}); + when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true); + ActivityManager.AppTask task = mock(ActivityManager.AppTask.class); List<ActivityManager.AppTask> taskList = new ArrayList<>(); ActivityManager.RecentTaskInfo taskInfo = new ActivityManager.RecentTaskInfo(); @@ -506,7 +509,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class), - mTelecomManager); + mTelecomManager, mLogger); // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); @@ -582,6 +585,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertNotNull(mBinderService.getNotificationChannel( PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID)); clearInvocations(mRankingHandler); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); } @After @@ -1234,8 +1238,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testEnqueuedBlockedNotifications_blockedApp() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); - - mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, @@ -1248,8 +1251,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testEnqueuedBlockedNotifications_blockedAppForegroundService() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); - - mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; @@ -1342,6 +1344,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSetNotificationsEnabledForPackage_noChange() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, true); + + verify(mPermissionHelper, never()).setNotificationPermission( + anyString(), anyInt(), anyBoolean(), anyBoolean()); + } + + @Test + public void testSetNotificationsEnabledForPackage() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false); + + verify(mPermissionHelper).setNotificationPermission( + mContext.getPackageName(), UserHandle.getUserId(mUid), false, true); + + verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt()); + List<NotificationChannelLoggerFake.CallRecord> calls = mLogger.getCalls(); + Assert.assertEquals( + NotificationChannelLogger.NotificationChannelEvent.APP_NOTIFICATIONS_BLOCKED, + calls.get(calls.size() -1).event); + } + + @Test public void testBlockedNotifications_blockedByAssistant() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); @@ -1368,7 +1394,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testBlockedNotifications_blockedByUser() throws Exception { - mService.setPreferencesHelper(mPreferencesHelper); when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); @@ -1377,7 +1402,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(channel); mService.addEnqueuedNotification(r); - when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(IMPORTANCE_NONE); + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false); NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), @@ -1390,8 +1415,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testEnqueueNotificationInternal_noChannel() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + NotificationRecord nr = generateNotificationRecord( + new NotificationChannel("did not create", "", IMPORTANCE_DEFAULT)); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + verify(mPermissionHelper).hasPermission(mUid); + verify(mPermissionHelper, never()).hasPermission(Process.SYSTEM_UID); + + reset(mPermissionHelper); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + verify(mPermissionHelper).hasPermission(mUid); + assertThat(mService.mChannelToastsSent).contains(mUid); + } + + @Test public void testEnqueueNotification_appBlocked() throws Exception { - mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testEnqueueNotification_appBlocked", 0, @@ -2686,6 +2735,49 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testDefaultChannelUpdatesApp_postMigrationToPermissions() throws Exception { + final NotificationChannel defaultChannel = mBinderService.getNotificationChannel( + PKG_N_MR1, ActivityManager.getCurrentUser(), PKG_N_MR1, + NotificationChannel.DEFAULT_CHANNEL_ID); + defaultChannel.setImportance(IMPORTANCE_NONE); + + mBinderService.updateNotificationChannelForPackage(PKG_N_MR1, mUid, defaultChannel); + + verify(mPermissionHelper).setNotificationPermission( + PKG_N_MR1, ActivityManager.getCurrentUser(), false, true); + } + + @Test + public void testPostNotification_appPermissionFixed() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true); + + NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel); + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testPostNotification_appPermissionFixed", 0, + temp.getNotification(), 0); + waitForIdle(); + assertThat(mService.getNotificationRecordCount()).isEqualTo(1); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(PKG); + assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue(); + } + + @Test + public void testSummaryNotification_appPermissionFixed() { + NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(temp); + + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true); + + NotificationRecord r = mService.createAutoGroupSummary( + temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false); + + assertThat(r.isImportanceFixed()).isTrue(); + } + + @Test public void testTvExtenderChannelOverride_onTv() throws Exception { mService.setIsTelevision(true); mService.setPreferencesHelper(mPreferencesHelper); @@ -2718,13 +2810,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateAppNotifyCreatorBlock() throws Exception { - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(PKG, mUid)).thenReturn(IMPORTANCE_DEFAULT); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - // should trigger a broadcast mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); Thread.sleep(500); waitForIdle(); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); @@ -2736,7 +2827,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception { - mService.setPreferencesHelper(mPreferencesHelper); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); mBinderService.setNotificationsEnabledForPackage(PKG, 0, false); verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); @@ -2744,15 +2835,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateAppNotifyCreatorUnblock() throws Exception { - mService.setPreferencesHelper(mPreferencesHelper); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - // should not trigger a broadcast - when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED); - - // should trigger a broadcast - mBinderService.setNotificationsEnabledForPackage(PKG, 0, true); + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true); Thread.sleep(500); waitForIdle(); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); @@ -4344,7 +4432,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(IMPORTANCE_LOW, mService.getNotificationRecord(sbn.getKey()).getImportance()); - assertEquals(IMPORTANCE_UNSPECIFIED, mBinderService.getPackageImportance( + assertEquals(IMPORTANCE_DEFAULT, mBinderService.getPackageImportance( sbn.getPackageName())); nb = new Notification.Builder(mContext) @@ -4860,6 +4948,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testBackup() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); int systemChecks = mService.countSystemChecks; when(mListeners.queryPackageForServices(anyString(), anyInt(), anyInt())) .thenReturn(new ArraySet<>()); @@ -5963,8 +6052,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(false); // notifications from this package are blocked by the user - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); setAppInForegroundForToasts(mUid, true); @@ -6260,8 +6348,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(false); // notifications from this package are blocked by the user - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); setAppInForegroundForToasts(mUid, false); @@ -6347,8 +6434,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(true); // notifications from this package are NOT blocked by the user - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_LOW); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); // enqueue toast -> no toasts enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -6369,8 +6455,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(false); // notifications from this package are blocked by the user - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); setAppInForegroundForToasts(mUid, false); @@ -6393,8 +6478,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(true); // notifications from this package ARE blocked by the user - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); setAppInForegroundForToasts(mUid, false); @@ -7303,6 +7387,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testAreNotificationsEnabledForPackage() throws Exception { + mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), + mUid); + + verify(mPermissionHelper).hasPermission(mUid); + } + + @Test public void testAreNotificationsEnabledForPackage_crossUser() throws Exception { try { mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), @@ -7311,21 +7403,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } catch (SecurityException e) { // pass } + verify(mPermissionHelper, never()).hasPermission(anyInt()); // cross user, with permission, no problem enableInteractAcrossUsers(); mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid + UserHandle.PER_USER_RANGE); - verify(mPermissionHelper, never()).hasPermission(anyInt()); + verify(mPermissionHelper).hasPermission(mUid + UserHandle.PER_USER_RANGE); } @Test - public void testAreNotificationsEnabledForPackage_viaInternalService() throws Exception { - assertEquals(mInternalService.areNotificationsEnabledForPackage( - mContext.getPackageName(), mUid), - mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid)); - verify(mPermissionHelper, never()).hasPermission(anyInt()); + public void testAreNotificationsEnabledForPackage_viaInternalService() { + mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid); + verify(mPermissionHelper).hasPermission(mUid); + } + + @Test + public void testGetPackageImportance() throws Exception { + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + assertThat(mBinderService.getPackageImportance(mContext.getPackageName())) + .isEqualTo(IMPORTANCE_DEFAULT); + + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + assertThat(mBinderService.getPackageImportance(mContext.getPackageName())) + .isEqualTo(IMPORTANCE_NONE); } @Test @@ -8975,48 +9077,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testMigrationDisabledByDefault() { - assertThat(mService.mEnableAppSettingMigration).isFalse(); - } - - @Test - public void testPostNotification_channelLockedFixed() throws Exception { - mTestNotificationChannel.setImportanceLockedByOEM(true); - - NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, - "testPostNotification_appPermissionFixed", 0, - temp.getNotification(), 0); - waitForIdle(); - assertThat(mService.getNotificationRecordCount()).isEqualTo(1); - StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(PKG); - assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue(); - - mBinderService.cancelAllNotifications(PKG, 0); - waitForIdle(); - - mTestNotificationChannel.setImportanceLockedByOEM(false); - mTestNotificationChannel.setImportanceLockedByCriticalDeviceFunction(true); - - temp = generateNotificationRecord(mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, - "testPostNotification_appPermissionFixed", 0, - temp.getNotification(), 0); - waitForIdle(); - assertThat(mService.getNotificationRecordCount()).isEqualTo(1); - notifs = mBinderService.getActiveNotifications(PKG); - assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue(); - } - - @Test public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException { mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getImportance(PKG, mUid)).thenReturn(IMPORTANCE_NONE); + + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); assertThat(mBinderService.getNotificationChannelsBypassingDnd(PKG, mUid).getList()) .isEmpty(); - verify(mPermissionHelper, never()).hasPermission(anyInt()); verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid); } @@ -9110,8 +9177,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.setNotificationsEnabledForPackage( - r.getSbn().getPackageName(), r.getUid(), false); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); // normal blocked notifications - blocked assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), @@ -9149,6 +9215,67 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testMediaNotificationsBypassBlock_atPost() throws Exception { + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + + Notification.Builder nb = new Notification.Builder( + mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(null, "test", null).build()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false); + + mService.addEnqueuedNotification(r); + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // just using the style - blocked + mService.clearNotifications(); + reset(mUsageStats); + nb.setStyle(new Notification.MediaStyle()); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // style + media session - bypasses block + mService.clearNotifications(); + reset(mUsageStats); + nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats, never()).registerBlocked(any()); + verify(mUsageStats).registerPostedByApp(any()); + } + + @Test public void testCallNotificationsBypassBlock() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); @@ -9163,8 +9290,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.setNotificationsEnabledForPackage( - r.getSbn().getPackageName(), r.getUid(), false); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); // normal blocked notifications - blocked assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), @@ -9194,12 +9320,151 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.getSbn().getPackageName(), r.getUser())).thenReturn(true); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + + // set telecom manager to null - blocked + mService.setTelecomManager(null); + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)) + .isFalse(); + + // set telecom feature to false - blocked + when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false); + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)) + .isFalse(); + } + + @Test + public void testCallNotificationsBypassBlock_atPost() throws Exception { + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + + Notification.Builder nb = + new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(null, "test", null).build()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + + // normal blocked notifications - blocked + mService.addEnqueuedNotification(r); + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // just using the style - blocked + mService.clearNotifications(); + reset(mUsageStats); + Person person = new Person.Builder().setName("caller").build(); + nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class))); + nb.setFullScreenIntent(mock(PendingIntent.class), true); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, nb.build(), + UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + runnable = mService.new PostNotificationRunnable( + r.getKey(), r.getSbn().getPackageName(), r.getUid(), SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // style + managed call - bypasses block + mService.clearNotifications(); + reset(mUsageStats); + when(mTelecomManager.isInManagedCall()).thenReturn(true); + + mService.addEnqueuedNotification(r); + runnable.run(); + waitForIdle(); + + verify(mUsageStats, never()).registerBlocked(any()); + verify(mUsageStats).registerPostedByApp(any()); + + // style + self managed call - bypasses block + mService.clearNotifications(); + reset(mUsageStats); + when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser())) + .thenReturn(true); + + mService.addEnqueuedNotification(r); + runnable.run(); + waitForIdle(); + + verify(mUsageStats, never()).registerBlocked(any()); + verify(mUsageStats).registerPostedByApp(any()); + + // set telecom manager to null - notifications should be blocked + // but post notifications runnable should not crash + mService.clearNotifications(); + reset(mUsageStats); + mService.setTelecomManager(null); + + mService.addEnqueuedNotification(r); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // set FEATURE_TELECOM to false - notifications should be blocked + // but post notifications runnable should not crash + mService.setTelecomManager(mTelecomManager); + when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false); + reset(mUsageStats); + mService.setTelecomManager(null); + + mService.addEnqueuedNotification(r); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); } @Test - public void testGetAllUsersNotificationPermissions_migrationNotEnabled() { - // make sure we don't bother if the migration is not enabled - assertThat(mService.getAllUsersNotificationPermissions()).isNull(); + public void testGetAllUsersNotificationPermissions() { + // In this case, there are multiple users each with notification permissions (and also, + // for good measure, some without). + // make sure the collection returned contains info for all of them + final List<UserInfo> userInfos = new ArrayList<>(); + userInfos.add(new UserInfo(0, "user0", 0)); + userInfos.add(new UserInfo(1, "user1", 0)); + userInfos.add(new UserInfo(2, "user2", 0)); + when(mUm.getUsers()).thenReturn(userInfos); + + // construct the permissions for each of them + ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> permissions0 = new ArrayMap<>(), + permissions1 = new ArrayMap<>(); + permissions0.put(new Pair<>(10, "package1"), new Pair<>(true, false)); + permissions0.put(new Pair<>(20, "package2"), new Pair<>(false, true)); + permissions1.put(new Pair<>(11, "package1"), new Pair<>(false, false)); + permissions1.put(new Pair<>(21, "package2"), new Pair<>(true, true)); + when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0); + when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1); + when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>()); + + ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> combinedPermissions = + mService.getAllUsersNotificationPermissions(); + assertTrue(combinedPermissions.get(new Pair<>(10, "package1")).first); + assertFalse(combinedPermissions.get(new Pair<>(10, "package1")).second); + assertFalse(combinedPermissions.get(new Pair<>(20, "package2")).first); + assertTrue(combinedPermissions.get(new Pair<>(20, "package2")).second); + assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).first); + assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).second); + assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).first); + assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).second); } @Test @@ -9308,6 +9573,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_unknown() { + reset(mMockNm); + // Set up various possible states of the settings int and confirm whether or not the // notification is shown as expected @@ -9321,6 +9588,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_shouldShow() { + reset(mMockNm); + // If state is SHOULD_SHOW, it ... should show Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, @@ -9333,6 +9602,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_alreadyShown() { + reset(mMockNm); + // If state is either USER_INTERACTED or DISMISSED, we should not show this on boot Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, @@ -9349,6 +9620,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_reshown() { + reset(mMockNm); + // If we have re-shown the notification and the user did not subsequently interacted with // it, then make sure we show when trying on boot Settings.Global.putInt(mContext.getContentResolver(), @@ -9362,6 +9635,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testRescheduledReviewPermissionsNotification() { + reset(mMockNm); + // when rescheduled, the notification goes through the NotificationManagerInternal service // this call doesn't need to know anything about previously scheduled state -- if called, // it should send the notification & write the appropriate int to Settings diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java deleted file mode 100755 index b751c7fc73ea..000000000000 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ /dev/null @@ -1,877 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.notification; - -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.MODE_IGNORED; -import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.PendingIntent.FLAG_MUTABLE; -import static android.app.PendingIntent.FLAG_ONE_SHOT; -import static android.content.pm.PackageManager.FEATURE_WATCH; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.UserHandle.USER_SYSTEM; - -import static com.google.common.truth.Truth.assertThat; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.app.ActivityManagerInternal; -import android.app.AlarmManager; -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.app.INotificationManager; -import android.app.IUriGrantsManager; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.StatsManager; -import android.app.admin.DevicePolicyManagerInternal; -import android.app.usage.UsageStatsManagerInternal; -import android.companion.ICompanionDeviceManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.IIntentSender; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.content.pm.ParceledListSlice; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutServiceInternal; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.media.AudioManager; -import android.media.session.MediaSession; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Looper; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.service.notification.NotificationListenerFilter; -import android.service.notification.StatusBarNotification; -import android.telecom.TelecomManager; -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; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.AtomicFile; -import android.util.Pair; - -import androidx.test.InstrumentationRegistry; - -import com.android.internal.app.IAppOpsService; -import com.android.internal.logging.InstanceIdSequence; -import com.android.internal.logging.InstanceIdSequenceFake; -import com.android.server.DeviceIdleInternal; -import com.android.server.LocalServices; -import com.android.server.SystemService; -import com.android.server.UiServiceTestCase; -import com.android.server.lights.LightsManager; -import com.android.server.lights.LogicalLight; -import com.android.server.notification.NotificationManagerService.NotificationAssistants; -import com.android.server.notification.NotificationManagerService.NotificationListeners; -import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.uri.UriGrantsManagerInternal; -import com.android.server.utils.quota.MultiRateLimiter; -import com.android.server.wm.ActivityTaskManagerInternal; -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.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.io.FileOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -/** - * Tests that NMS reads/writes the app notification state from Package/PermissionManager when - * migration is enabled. Because the migration field is read onStart - * TODO (b/194833441): migrate these tests to NotificationManagerServiceTest when the migration is - * permanently enabled. - */ -public class NotificationPermissionMigrationTest extends UiServiceTestCase { - private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; - private static final int UID_HEADLESS = 1000000; - - private final int mUid = Binder.getCallingUid(); - private TestableNotificationManagerService mService; - private INotificationManager mBinderService; - private NotificationManagerInternal mInternalService; - private ShortcutHelper mShortcutHelper; - @Mock - private IPackageManager mPackageManager; - @Mock - private PackageManager mPackageManagerClient; - @Mock - private PackageManagerInternal mPackageManagerInternal; - @Mock - private WindowManagerInternal mWindowManagerInternal; - @Mock - private PermissionHelper mPermissionHelper; - private TestableContext mContext = spy(getContext()); - private final String PKG = mContext.getPackageName(); - private TestableLooper mTestableLooper; - @Mock - private RankingHelper mRankingHelper; - @Mock private PreferencesHelper mPreferencesHelper; - AtomicFile mPolicyFile; - File mFile; - @Mock - private NotificationUsageStats mUsageStats; - @Mock - private UsageStatsManagerInternal mAppUsageStats; - @Mock - private AudioManager mAudioManager; - @Mock - private LauncherApps mLauncherApps; - @Mock - private ShortcutServiceInternal mShortcutServiceInternal; - @Mock - private UserManager mUserManager; - @Mock - ActivityManager mActivityManager; - @Mock - Resources mResources; - @Mock - RankingHandler mRankingHandler; - @Mock - ActivityManagerInternal mAmi; - @Mock - private Looper mMainLooper; - - @Mock - IIntentSender pi1; - - private static final int MAX_POST_DELAY = 1000; - - private NotificationChannel mTestNotificationChannel = new NotificationChannel( - TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); - - private static final String VALID_CONVO_SHORTCUT_ID = "shortcut"; - - @Mock - private NotificationListeners mListeners; - @Mock - private NotificationListenerFilter mNlf; - @Mock private NotificationAssistants mAssistants; - @Mock private ConditionProviders mConditionProviders; - private ManagedServices.ManagedServiceInfo mListener; - @Mock private ICompanionDeviceManager mCompanionMgr; - @Mock SnoozeHelper mSnoozeHelper; - @Mock GroupHelper mGroupHelper; - @Mock - IBinder mPermOwner; - @Mock - IActivityManager mAm; - @Mock - ActivityTaskManagerInternal mAtm; - @Mock - IUriGrantsManager mUgm; - @Mock - UriGrantsManagerInternal mUgmInternal; - @Mock - AppOpsManager mAppOpsManager; - @Mock - private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback - mNotificationAssistantAccessGrantedCallback; - @Mock - UserManager mUm; - @Mock - NotificationHistoryManager mHistoryManager; - @Mock - StatsManager mStatsManager; - @Mock - AlarmManager mAlarmManager; - @Mock - MultiRateLimiter mToastRateLimiter; - BroadcastReceiver mPackageIntentReceiver; - NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); - private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( - 1 << 30); - @Mock - StatusBarManagerInternal mStatusBar; - - private NotificationManagerService.WorkerHandler mWorkerHandler; - - @Before - public void setUp() throws Exception { - // These should be the only difference in setup from NMSTest - Settings.Secure.putIntForUser( - getContext().getContentResolver(), - Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 1, USER_SYSTEM); - Settings.Global.putInt(getContext().getContentResolver(), - Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 1); - - // Shell permissions will override permissions of our app, so add all necessary permissions - // for this test here: - InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( - "android.permission.WRITE_DEVICE_CONFIG", - "android.permission.READ_DEVICE_CONFIG", - "android.permission.READ_CONTACTS"); - - MockitoAnnotations.initMocks(this); - - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - - DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class); - when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L); - - LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); - LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); - LocalServices.removeServiceForTest(WindowManagerInternal.class); - LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); - LocalServices.removeServiceForTest(StatusBarManagerInternal.class); - LocalServices.addService(StatusBarManagerInternal.class, mStatusBar); - LocalServices.removeServiceForTest(DeviceIdleInternal.class); - LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal); - LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.addService(ActivityManagerInternal.class, mAmi); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); - mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); - when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0}); - - doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); - - mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger, - mNotificationInstanceIdSequence); - - // Use this testable looper. - mTestableLooper = TestableLooper.get(this); - // MockPackageManager - default returns ApplicationInfo with matching calling UID - mContext.setMockPackageManager(mPackageManagerClient); - - when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt())) - .thenAnswer((Answer<ApplicationInfo>) invocation -> { - Object[] args = invocation.getArguments(); - return getApplicationInfo((String) args[0], mUid); - }); - when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) - .thenAnswer((Answer<ApplicationInfo>) invocation -> { - Object[] args = invocation.getArguments(); - return getApplicationInfo((String) args[0], mUid); - }); - when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid); - when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenAnswer( - (Answer<Boolean>) invocation -> { - Object[] args = invocation.getArguments(); - return (int) args[1] == mUid; - }); - final LightsManager mockLightsManager = mock(LightsManager.class); - when(mockLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class)); - when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); - when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); - when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); - when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG}); - when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG}); - mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); - - // write to a test file; the system file isn't readable from tests - mFile = new File(mContext.getCacheDir(), "test.xml"); - mFile.createNewFile(); - final String preupgradeXml = "<notification-policy></notification-policy>"; - mPolicyFile = new AtomicFile(mFile); - FileOutputStream fos = mPolicyFile.startWrite(); - fos.write(preupgradeXml.getBytes()); - mPolicyFile.finishWrite(fos); - - // Setup managed services - when(mNlf.isTypeAllowed(anyInt())).thenReturn(true); - when(mNlf.isPackageAllowed(any())).thenReturn(true); - when(mNlf.isPackageAllowed(null)).thenReturn(true); - when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf); - mListener = mListeners.new ManagedServiceInfo( - null, new ComponentName(PKG, "test_class"), - UserHandle.getUserId(mUid), true, null, 0, 123); - ComponentName defaultComponent = ComponentName.unflattenFromString("config/device"); - ArraySet<ComponentName> components = new ArraySet<>(); - components.add(defaultComponent); - when(mListeners.getDefaultComponents()).thenReturn(components); - when(mConditionProviders.getDefaultPackages()) - .thenReturn(new ArraySet<>(Arrays.asList("config"))); - when(mAssistants.getDefaultComponents()).thenReturn(components); - when(mAssistants.queryPackageForServices( - anyString(), anyInt(), anyInt())).thenReturn(components); - when(mListeners.checkServiceTokenLocked(null)).thenReturn(mListener); - ManagedServices.Config listenerConfig = new ManagedServices.Config(); - listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS; - when(mListeners.getConfig()).thenReturn(listenerConfig); - ManagedServices.Config assistantConfig = new ManagedServices.Config(); - assistantConfig.xmlTag = NotificationAssistants.TAG_ENABLED_NOTIFICATION_ASSISTANTS; - when(mAssistants.getConfig()).thenReturn(assistantConfig); - ManagedServices.Config dndConfig = new ManagedServices.Config(); - dndConfig.xmlTag = ConditionProviders.TAG_ENABLED_DND_APPS; - when(mConditionProviders.getConfig()).thenReturn(dndConfig); - - when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true); - - // apps allowed as convos - mService.setStringArrayResourceValue(PKG_O); - - mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); - mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, - mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, - mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm, - mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, - mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager, - mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper, - mock(UsageStatsManagerInternal.class), mock(TelecomManager.class)); - // Return first true for RoleObserver main-thread check - when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); - mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); - - mService.setAudioManager(mAudioManager); - - mShortcutHelper = mService.getShortcutHelper(); - mShortcutHelper.setLauncherApps(mLauncherApps); - mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal); - mShortcutHelper.setUserManager(mUserManager); - - // Capture PackageIntentReceiver - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - ArgumentCaptor<IntentFilter> intentFilterCaptor = - ArgumentCaptor.forClass(IntentFilter.class); - - verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(), - any(), intentFilterCaptor.capture(), any(), any()); - verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(), - intentFilterCaptor.capture()); - List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues(); - List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues(); - - for (int i = 0; i < intentFilters.size(); i++) { - final IntentFilter filter = intentFilters.get(i); - if (filter.hasAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED) - && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) - && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { - mPackageIntentReceiver = broadcastReceivers.get(i); - } - } - assertNotNull("package intent receiver should exist", mPackageIntentReceiver); - - // Pretend the shortcut exists - List<ShortcutInfo> shortcutInfos = new ArrayList<>(); - ShortcutInfo info = mock(ShortcutInfo.class); - when(info.getPackage()).thenReturn(PKG); - when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID); - when(info.getUserId()).thenReturn(USER_SYSTEM); - when(info.isLongLived()).thenReturn(true); - when(info.isEnabled()).thenReturn(true); - shortcutInfos.add(info); - when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); - when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), - anyString(), anyInt(), any())).thenReturn(true); - when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true); - - // Set the testable bubble extractor - RankingHelper rankingHelper = mService.getRankingHelper(); - BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class); - extractor.setActivityManager(mActivityManager); - - // Tests call directly into the Binder. - mBinderService = mService.getBinderService(); - mInternalService = mService.getInternalService(); - - mBinderService.createNotificationChannels( - PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); - mBinderService.createNotificationChannels( - PKG_P, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); - mBinderService.createNotificationChannels( - PKG_O, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); - assertNotNull(mBinderService.getNotificationChannel( - PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID)); - clearInvocations(mRankingHandler); - } - - @After - public void tearDown() throws Exception { - if (mFile != null) mFile.delete(); - - try { - mService.onDestroy(); - } catch (IllegalStateException | IllegalArgumentException e) { - // can throw if a broadcast receiver was never registered - } - - InstrumentationRegistry.getInstrumentation() - .getUiAutomation().dropShellPermissionIdentity(); - // Remove scheduled messages that would be processed when the test is already done, and - // could cause issues, for example, messages that remove/cancel shown toasts (this causes - // problematic interactions with mocks when they're no longer working as expected). - mWorkerHandler.removeCallbacksAndMessages(null); - } - - private ApplicationInfo getApplicationInfo(String pkg, int uid) { - final ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.uid = uid; - switch (pkg) { - case PKG_N_MR1: - applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1; - break; - case PKG_O: - applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; - break; - case PKG_P: - applicationInfo.targetSdkVersion = Build.VERSION_CODES.P; - break; - default: - applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - break; - } - return applicationInfo; - } - - public void waitForIdle() { - mTestableLooper.processAllMessages(); - } - - private NotificationRecord generateNotificationRecord(NotificationChannel channel) { - return generateNotificationRecord(channel, null); - } - - private NotificationRecord generateNotificationRecord(NotificationChannel channel, - Notification.TvExtender extender) { - if (channel == null) { - channel = mTestNotificationChannel; - } - Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) - .setContentTitle("foo") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addAction(new Notification.Action.Builder(null, "test", null).build()); - if (extender != null) { - nb.extend(extender); - } - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - return new NotificationRecord(mContext, sbn, channel); - } - - private void enableInteractAcrossUsers() { - TestablePermissions perms = mContext.getTestablePermissions(); - perms.setPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, PERMISSION_GRANTED); - } - - @Test - public void testAreNotificationsEnabledForPackage() throws Exception { - mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), - mUid); - - verify(mPermissionHelper).hasPermission(mUid); - } - - @Test - public void testAreNotificationsEnabledForPackage_crossUser() throws Exception { - try { - mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), - mUid + UserHandle.PER_USER_RANGE); - fail("Cannot call cross user without permission"); - } catch (SecurityException e) { - // pass - } - verify(mPermissionHelper, never()).hasPermission(anyInt()); - - // cross user, with permission, no problem - enableInteractAcrossUsers(); - mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), - mUid + UserHandle.PER_USER_RANGE); - - verify(mPermissionHelper).hasPermission(mUid + UserHandle.PER_USER_RANGE); - } - - @Test - public void testAreNotificationsEnabledForPackage_viaInternalService() { - mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid); - verify(mPermissionHelper).hasPermission(mUid); - } - - @Test - public void testGetPackageImportance() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - assertThat(mBinderService.getPackageImportance(mContext.getPackageName())) - .isEqualTo(IMPORTANCE_DEFAULT); - - when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - assertThat(mBinderService.getPackageImportance(mContext.getPackageName())) - .isEqualTo(IMPORTANCE_NONE); - } - - @Test - public void testEnqueueNotificationInternal_noChannel() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - NotificationRecord nr = generateNotificationRecord( - new NotificationChannel("did not create", "", IMPORTANCE_DEFAULT)); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - verify(mPermissionHelper).hasPermission(mUid); - verify(mPermissionHelper, never()).hasPermission(Process.SYSTEM_UID); - - reset(mPermissionHelper); - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - verify(mPermissionHelper).hasPermission(mUid); - assertThat(mService.mChannelToastsSent).contains(mUid); - } - - @Test - public void testSetNotificationsEnabledForPackage_noChange() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, true); - - verify(mPermissionHelper, never()).setNotificationPermission( - anyString(), anyInt(), anyBoolean(), anyBoolean()); - } - - @Test - public void testSetNotificationsEnabledForPackage() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false); - - verify(mPermissionHelper).setNotificationPermission( - mContext.getPackageName(), UserHandle.getUserId(mUid), false, true); - - verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt()); - } - - @Test - public void testUpdateAppNotifyCreatorBlock() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - - mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); - Thread.sleep(500); - waitForIdle(); - - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); - - assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, - captor.getValue().getAction()); - assertEquals(PKG, captor.getValue().getPackage()); - assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); - } - - @Test - public void testUpdateAppNotifyCreatorUnblock() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - - mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true); - Thread.sleep(500); - waitForIdle(); - - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); - - assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, - captor.getValue().getAction()); - assertEquals(PKG, captor.getValue().getPackage()); - assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); - } - - @Test - public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException { - mService.setPreferencesHelper(mPreferencesHelper); - - when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - - assertThat(mBinderService.getNotificationChannelsBypassingDnd(PKG, mUid).getList()) - .isEmpty(); - verify(mPreferencesHelper, never()).getImportance(anyString(), anyInt()); - verify(mPreferencesHelper, never()).getNotificationChannelsBypassingDnd(PKG, mUid); - } - - @Test - public void testBlockedNotifications_blockedByUser() throws Exception { - when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); - when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); - - NotificationChannel channel = new NotificationChannel("id", "name", - NotificationManager.IMPORTANCE_HIGH); - NotificationRecord r = generateNotificationRecord(channel); - mService.addEnqueuedNotification(r); - - when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false); - - NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); - runnable.run(); - waitForIdle(); - - verify(mUsageStats).registerBlocked(any()); - verify(mUsageStats, never()).registerPostedByApp(any()); - } - - @Test - public void testEnqueueNotification_appBlocked() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, - "testEnqueueNotification_appBlocked", 0, - generateNotificationRecord(null).getNotification(), 0); - waitForIdle(); - verify(mWorkerHandler, never()).post( - any(NotificationManagerService.EnqueueNotificationRunnable.class)); - } - - @Test - public void testDefaultChannelDoesNotUpdateApp_postMigrationToPermissions() throws Exception { - final NotificationChannel defaultChannel = mBinderService.getNotificationChannel( - PKG_N_MR1, ActivityManager.getCurrentUser(), PKG_N_MR1, - NotificationChannel.DEFAULT_CHANNEL_ID); - defaultChannel.setImportance(IMPORTANCE_NONE); - - mBinderService.updateNotificationChannelForPackage(PKG_N_MR1, mUid, defaultChannel); - - verify(mPermissionHelper).setNotificationPermission( - PKG_N_MR1, ActivityManager.getCurrentUser(), false, true); - } - - @Test - public void testPostNotification_appPermissionFixed() throws Exception { - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true); - - NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, - "testPostNotification_appPermissionFixed", 0, - temp.getNotification(), 0); - waitForIdle(); - assertThat(mService.getNotificationRecordCount()).isEqualTo(1); - StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(PKG); - assertThat(mService.getNotificationRecord(notifs[0].getKey()).isImportanceFixed()).isTrue(); - } - - @Test - public void testSummaryNotification_appPermissionFixed() { - NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel); - mService.addNotification(temp); - - when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true); - - NotificationRecord r = mService.createAutoGroupSummary( - temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false); - - assertThat(r.isImportanceFixed()).isTrue(); - } - - @Test - public void testMediaNotificationsBypassBlock() throws Exception { - when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) - .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); - when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); - - Notification.Builder nb = new Notification.Builder( - mContext, mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addAction(new Notification.Action.Builder(null, "test", null).build()); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - - // normal blocked notifications - blocked - assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); - - // just using the style - blocked - nb.setStyle(new Notification.MediaStyle()); - sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); - - // using the style, but incorrect type in session - blocked - nb.setStyle(new Notification.MediaStyle()); - Bundle extras = new Bundle(); - extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent()); - nb.addExtras(extras); - sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); - - // style + media session - bypasses block - nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); - sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); - } - - @Test - public void testMediaNotificationsBypassBlock_atPost() throws Exception { - when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); - when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); - - Notification.Builder nb = new Notification.Builder( - mContext, mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addAction(new Notification.Action.Builder(null, "test", null).build()); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false); - - mService.addEnqueuedNotification(r); - NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); - runnable.run(); - waitForIdle(); - - verify(mUsageStats).registerBlocked(any()); - verify(mUsageStats, never()).registerPostedByApp(any()); - - // just using the style - blocked - mService.clearNotifications(); - reset(mUsageStats); - nb.setStyle(new Notification.MediaStyle()); - sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mService.addEnqueuedNotification(r); - runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); - runnable.run(); - waitForIdle(); - - verify(mUsageStats).registerBlocked(any()); - verify(mUsageStats, never()).registerPostedByApp(any()); - - // style + media session - bypasses block - mService.clearNotifications(); - reset(mUsageStats); - nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); - sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mService.addEnqueuedNotification(r); - runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); - runnable.run(); - waitForIdle(); - - verify(mUsageStats, never()).registerBlocked(any()); - verify(mUsageStats).registerPostedByApp(any()); - } - - @Test - public void testGetAllUsersNotificationPermissions() { - // In this case, there are multiple users each with notification permissions (and also, - // for good measure, some without). - // make sure the collection returned contains info for all of them - final List<UserInfo> userInfos = new ArrayList<>(); - userInfos.add(new UserInfo(0, "user0", 0)); - userInfos.add(new UserInfo(1, "user1", 0)); - userInfos.add(new UserInfo(2, "user2", 0)); - when(mUm.getUsers()).thenReturn(userInfos); - - // construct the permissions for each of them - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> permissions0 = new ArrayMap<>(), - permissions1 = new ArrayMap<>(); - permissions0.put(new Pair<>(10, "package1"), new Pair<>(true, false)); - permissions0.put(new Pair<>(20, "package2"), new Pair<>(false, true)); - permissions1.put(new Pair<>(11, "package1"), new Pair<>(false, false)); - permissions1.put(new Pair<>(21, "package2"), new Pair<>(true, true)); - when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0); - when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1); - when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>()); - - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> combinedPermissions = - mService.getAllUsersNotificationPermissions(); - assertTrue(combinedPermissions.get(new Pair<>(10, "package1")).first); - assertFalse(combinedPermissions.get(new Pair<>(10, "package1")).second); - assertFalse(combinedPermissions.get(new Pair<>(20, "package2")).first); - assertTrue(combinedPermissions.get(new Pair<>(20, "package2")).second); - assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).first); - assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).second); - assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).first); - assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).second); - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index d89141cc1000..5468220d9564 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -1065,9 +1065,8 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test - public void testApplyImportanceAdjustmentsForNonOemDefaultAppLockedChannels() { + public void testApplyImportanceAdjustments() { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); - channel.setImportanceLockedByOEM(false); StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java index 46b47f4dcfdd..9b1d9c4eed22 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java @@ -26,8 +26,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.google.common.truth.Truth.assertThat; -import static junit.framework.Assert.fail; - import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -54,7 +52,6 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; @@ -62,14 +59,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; -import java.lang.reflect.Type; -import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; @SmallTest @@ -88,51 +78,13 @@ public class PermissionHelperTest extends UiServiceTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true, false); + mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager); PackageInfo testPkgInfo = new PackageInfo(); testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS }; when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt())) .thenReturn(testPkgInfo); } - // TODO (b/194833441): Remove when the migration is enabled - @Test - public void testMethodsThrowIfMigrationDisabled() throws IllegalAccessException, - InvocationTargetException { - PermissionHelper permHelper = - new PermissionHelper(mPmi, mPackageManager, mPermManager, false, false); - - Method[] allMethods = PermissionHelper.class.getDeclaredMethods(); - for (Method method : allMethods) { - if (Modifier.isPublic(method.getModifiers()) && - !Objects.equals("isMigrationEnabled", method.getName())) { - Parameter[] params = method.getParameters(); - List<Object> args = Lists.newArrayListWithCapacity(params.length); - for (int i = 0; i < params.length; i++) { - Type type = params[i].getParameterizedType(); - if (type.getTypeName().equals("java.lang.String")) { - args.add(""); - } else if (type.getTypeName().equals("boolean")){ - args.add(false); - } else if (type.getTypeName().equals("int")) { - args.add(1); - } else if (type.getTypeName().equals( - "com.android.server.notification.PermissionHelper$PackagePermission")) { - args.add(null); - } - } - try { - method.invoke(permHelper, args.toArray()); - fail("Method should have thrown because migration flag is disabled"); - } catch (InvocationTargetException e) { - if (!(e.getTargetException() instanceof IllegalStateException)) { - throw e; - } - } - } - } - } - @Test public void testHasPermission() throws Exception { when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt())) @@ -250,61 +202,8 @@ public class PermissionHelperTest extends UiServiceTestCase { } @Test - public void testSetNotificationPermission_pkgPerm_grantReviewRequired() throws Exception { - when(mPmi.checkPermission(anyString(), anyString(), anyInt())) - .thenReturn(PERMISSION_DENIED); - - PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission( - "pkg", 10, true, false); - mPermissionHelper.setNotificationPermission(pkgPerm); - - verify(mPermManager, never()).revokeRuntimePermission( - "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper"); - verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS, - FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10); - } - - @Test - public void testSetNotificationPermission_pkgPerm_notUserSet_grantedByDefaultPermNotSet() - throws Exception { - when(mPmi.checkPermission(anyString(), anyString(), anyInt())) - .thenReturn(PERMISSION_DENIED); - when(mPermManager.getPermissionFlags(anyString(), - eq(Manifest.permission.POST_NOTIFICATIONS), - anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT); - PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission( - "pkg", 10, true, false); - - mPermissionHelper.setNotificationPermission(pkgPerm); - verify(mPermManager, never()).revokeRuntimePermission( - anyString(), anyString(), anyInt(), anyString()); - verify(mPermManager, never()).updatePermissionFlags( - anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt()); - } - - @Test - public void testSetNotificationPermission_pkgPerm_userSet_grantedByDefaultPermSet() - throws Exception { - when(mPmi.checkPermission(anyString(), anyString(), anyInt())) - .thenReturn(PERMISSION_DENIED); - when(mPermManager.getPermissionFlags(anyString(), - eq(Manifest.permission.POST_NOTIFICATIONS), - anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT); - PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission( - "pkg", 10, true, true); - - mPermissionHelper.setNotificationPermission(pkgPerm); - verify(mPermManager).grantRuntimePermission( - "pkg", Manifest.permission.POST_NOTIFICATIONS, 10); - verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS, - FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_REVIEW_REQUIRED, - FLAG_PERMISSION_USER_SET, true, 10); - } - - @Test public void testSetNotificationPermission_pkgPerm_grantedByDefaultPermSet_allUserSet() throws Exception { - mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true, true); when(mPmi.checkPermission(anyString(), anyString(), anyInt())) .thenReturn(PERMISSION_DENIED); when(mPermManager.getPermissionFlags(anyString(), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 6d0895935877..8d50ceaf74e9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -85,6 +85,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; import android.media.AudioAttributes; @@ -121,6 +122,8 @@ import com.android.os.AtomsProto.PackageNotificationPreferences; import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; +import com.google.common.collect.ImmutableList; + import org.json.JSONArray; import org.json.JSONObject; import org.junit.Before; @@ -278,6 +281,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT); + ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false)); + appPermissions.put(new Pair(UID_O, PKG_O), new Pair(true, false)); + appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false)); + + when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) + .thenReturn(appPermissions); + mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, @@ -408,6 +419,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH); assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false)); + ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(uid0, package0), new Pair(false, false)); + appPermissions.put(new Pair(uid10, package10), new Pair(true, false)); + + when(mPermissionHelper.getNotificationPermissionValues(10)) + .thenReturn(appPermissions); + ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10); // Reset state. @@ -433,6 +451,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH); assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false)); + ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(uid0, package0), new Pair(true, false)); + + when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) + .thenReturn(appPermissions); + ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0); // Reset state. @@ -478,7 +502,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false)); mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); - mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_ALL, channel1.getId(), channel2.getId(), @@ -489,7 +512,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { loadStreamXml(baos, false, UserHandle.USER_ALL); assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); - assertTrue(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1)); assertEquals(channel1, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false)); compareChannels(channel2, @@ -550,8 +572,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true); mHelper.setValidBubbleSent(PKG_P, UID_P); - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE); - ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID); @@ -562,7 +582,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { loadStreamXml(baos, true, USER_SYSTEM); - assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_O, UID_O)); assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); assertTrue(mHelper.hasSentInvalidMsg(PKG_P, UID_P)); assertFalse(mHelper.hasSentInvalidMsg(PKG_N_MR1, UID_N_MR1)); @@ -601,7 +620,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migrates() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -672,7 +690,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -751,7 +768,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -809,7 +825,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -866,7 +881,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migration_NoUid() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -900,7 +914,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_NoUid() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -933,7 +946,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForNonBackup_postMigration() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -1014,7 +1026,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -1101,7 +1112,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noExternal() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -1181,7 +1191,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -1303,6 +1312,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testBackupRestoreXml_withNullSoundUri() throws Exception { + ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); + appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false)); + + when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) + .thenReturn(appPermissions); + NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); channel.setSound(null, mAudioAttributes); @@ -1472,14 +1487,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testCreateChannel_blocked() throws Exception { - mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE); - - assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, - new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false)); - } - - @Test public void testCreateChannel_badImportance() throws Exception { try { mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, @@ -1543,12 +1550,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testUpdate_preUpgrade_updatesAppFields() throws Exception { - mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_UNSPECIFIED); assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1)); assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1)); - assertFalse(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1)); NotificationChannel defaultChannel = mHelper.getNotificationChannel( PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false); @@ -1566,8 +1571,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1)); assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1)); - assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_N_MR1, UID_N_MR1)); - assertTrue(mHelper.getIsAppImportanceLocked(PKG_N_MR1, UID_N_MR1)); } @Test @@ -1592,9 +1595,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O)); assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, mHelper.getPackageVisibility(PKG_O, UID_O)); - assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O, - UID_O)); - assertFalse(mHelper.getIsAppImportanceLocked(PKG_O, UID_O)); } @Test @@ -1629,8 +1629,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1)); assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1)); - assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1, - UID_N_MR1)); } @Test @@ -2015,8 +2013,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() throws Exception { + public void testCreateAndDeleteCanChannelsBypassDnd_localSettings() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; + when(mPermissionHelper.hasPermission(uid)).thenReturn(true); // create notification channel that can't bypass dnd // expected result: areChannelsBypassingDnd = false @@ -2029,7 +2028,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { // create notification channel that can bypass dnd // expected result: areChannelsBypassingDnd = true - assertTrue(mHelper.getImportance(PKG_N_MR1, uid) != IMPORTANCE_NONE); NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW); channel2.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true); @@ -2052,8 +2050,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testCreateAndUpdateChannelsBypassingDnd_permissionHelper() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; - - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); when(mPermissionHelper.hasPermission(uid)).thenReturn(true); // create notification channel that can't bypass dnd @@ -2076,10 +2072,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testCreateAndDeleteCanChannelsBypassDnd_permissionHelper() throws Exception { + public void testCreateAndDeleteCanChannelsBypassDnd_permissionHelper() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; - - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); when(mPermissionHelper.hasPermission(uid)).thenReturn(true); // create notification channel that can't bypass dnd @@ -2113,8 +2107,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testBlockedGroupDoesNotBypassDnd() throws Exception { + public void testBlockedGroupDoesNotBypassDnd() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; + when(mPermissionHelper.hasPermission(uid)).thenReturn(true); // start in a 'allowed to bypass dnd state' mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, @@ -2140,8 +2135,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testBlockedAppsDoNotBypassDnd_localSettings() throws Exception { + public void testBlockedAppsDoNotBypassDnd_localSettings() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; + when(mPermissionHelper.hasPermission(uid)).thenReturn(false); // start in a 'allowed to bypass dnd state' mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, @@ -2151,7 +2147,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); - mHelper.setImportance(PKG_N_MR1, uid, IMPORTANCE_NONE); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW); @@ -2163,10 +2158,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testBlockedAppsDoNotBypassDnd_permissionHelper() throws Exception { + public void testBlockedAppsDoNotBypassDnd_permissionHelper() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); when(mPermissionHelper.hasPermission(uid)).thenReturn(false); + // start in a 'allowed to bypass dnd state' mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); @@ -2186,8 +2181,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testUpdateCanChannelsBypassDnd() throws Exception { + public void testUpdateCanChannelsBypassDnd() { int uid = UserManager.isHeadlessSystemUserMode() ? UID_HEADLESS : UID_N_MR1; + when(mPermissionHelper.hasPermission(uid)).thenReturn(true); // create notification channel that can't bypass dnd // expected result: areChannelsBypassingDnd = false @@ -2405,8 +2401,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy); // create records with the default channel for all user 0 and user 1 uids - mHelper.getImportance(PKG_N_MR1, user0Uids[i]); - mHelper.getImportance(PKG_N_MR1, user1Uids[i]); + mHelper.canShowBadge(PKG_N_MR1, user0Uids[i]); + mHelper.canShowBadge(PKG_N_MR1, user1Uids[i]); } mHelper.onUserRemoved(1); @@ -2445,17 +2441,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testOnPackageChanged_packageRemoval_importance() throws Exception { - mHelper.setImportance(PKG_N_MR1, UID_N_MR1, NotificationManager.IMPORTANCE_HIGH); - - mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{ - UID_N_MR1}); - - assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1, - UID_N_MR1)); - } - - @Test public void testOnPackageChanged_packageRemoval_groups() throws Exception { NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1"); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); @@ -2496,17 +2481,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false); mHelper.createNotificationChannelGroup( PKG_O, UID_O, new NotificationChannelGroup("1", "bye"), true); - mHelper.lockChannelsForOEM(pkg.toArray(new String[]{})); mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, pkgPair); mHelper.setNotificationDelegate(PKG_O, UID_O, "", 1); - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE); mHelper.setBubblesAllowed(PKG_O, UID_O, DEFAULT_BUBBLE_PREFERENCE); mHelper.setShowBadge(PKG_O, UID_O, false); mHelper.setAppImportanceLocked(PKG_O, UID_O); mHelper.clearData(PKG_O, UID_O); - assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O, UID_O)); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), DEFAULT_BUBBLE_PREFERENCE); assertTrue(mHelper.canShowBadge(PKG_O, UID_O)); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2518,13 +2500,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); assertTrue(channel.isImportanceLockedByCriticalDeviceFunction()); - assertTrue(channel.isImportanceLockedByOEM()); } @Test public void testRecordDefaults() throws Exception { - assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1, - UID_N_MR1)); assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); } @@ -2760,69 +2739,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testDumpJson_prePermissionMigration() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(false); - // before the migration is active, we want to verify that: - // - all notification importance info should come from package preferences - // - if there are permissions granted or denied from packages PreferencesHelper doesn't - // know about, those are ignored if migration is not enabled - - // package permissions map to be passed in - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); - appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs - appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs - appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false)); // in local prefs - appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs - - NotificationChannel channel1 = - new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); - NotificationChannel channel3 = new NotificationChannel("id3", "name3", IMPORTANCE_HIGH); - - mHelper.createNotificationChannel(PKG_P, UID_P, channel1, true, false); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false); - mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE); - mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false); - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - - // in the json array, all of the individual package preferences are simply elements in the - // values array. this set is to collect expected outputs for each of our packages. - // the key/value pairs are: (userId, package name) -> expected importance - ArrayMap<Pair<Integer, String>, String> expected = new ArrayMap<>(); - expected.put(new Pair(UserHandle.getUserId(UID_P), PKG_P), "LOW"); - expected.put(new Pair(UserHandle.getUserId(UID_O), PKG_O), "HIGH"); - expected.put(new Pair(UserHandle.getUserId(UID_N_MR1), PKG_N_MR1), "NONE"); - - JSONArray actual = (JSONArray) mHelper.dumpJson( - new NotificationManagerService.DumpFilter(), appPermissions) - .get("PackagePreferencess"); - assertThat(actual.length()).isEqualTo(expected.size()); - for (int i = 0; i < actual.length(); i++) { - JSONObject pkgInfo = actual.getJSONObject(i); - Pair<Integer, String> pkgKey = - new Pair(pkgInfo.getInt("userId"), pkgInfo.getString("packageName")); - assertTrue(expected.containsKey(pkgKey)); - assertThat(pkgInfo.getString("importance")).isEqualTo(expected.get(pkgKey)); - } - - // also make sure that (more likely to actually happen) if we don't provide an array of - // app preferences (and do null instead), the same thing happens, so do the same checks - JSONArray actualWithNullInput = (JSONArray) mHelper.dumpJson( - new NotificationManagerService.DumpFilter(), null) - .get("PackagePreferencess"); - assertThat(actualWithNullInput.length()).isEqualTo(expected.size()); - for (int i = 0; i < actualWithNullInput.length(); i++) { - JSONObject pkgInfo = actualWithNullInput.getJSONObject(i); - Pair<Integer, String> pkgKey = - new Pair(pkgInfo.getInt("userId"), pkgInfo.getString("packageName")); - assertTrue(expected.containsKey(pkgKey)); - assertThat(pkgInfo.getString("importance")).isEqualTo(expected.get(pkgKey)); - } - } - - @Test public void testDumpJson_postPermissionMigration() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); // when getting a json dump, we want to verify that: // - all notification importance info should come from the permission, even if the data // isn't there yet but is present in package preferences @@ -2844,11 +2761,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_P, UID_P, channel1, true, false); mHelper.createNotificationChannel(PKG_P, UID_P, channel2, false, false); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_LOW); mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, false, false); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false); - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); // in the json array, all of the individual package preferences are simply elements in the // values array. this set is to collect expected outputs for each of our packages. @@ -2887,11 +2801,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDumpJson_givenNullInput_postMigration() throws Exception { // simple test just to make sure nothing dies if we pass in null input even post migration // for some reason, even though in practice this should not be how one calls this method - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - // some packages exist, with some importance info that won't be looked at - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); + // some packages exist + mHelper.canShowBadge(PKG_O, UID_O); + mHelper.canShowBadge(PKG_P, UID_P); JSONArray actual = (JSONArray) mHelper.dumpJson( new NotificationManagerService.DumpFilter(), null) @@ -2908,44 +2821,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testDumpBansJson_prePermissionMigration() throws Exception { - // confirm that the package bans that are in json are only from package preferences, and - // not from the passed-in permissions map - when(mPermissionHelper.isMigrationEnabled()).thenReturn(false); - - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); - appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs - appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs - appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs - - // package preferences: only PKG_P is banned - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); - - // make sure that's the only thing in the package ban output - JSONArray actual = mHelper.dumpBansJson( - new NotificationManagerService.DumpFilter(), appPermissions); - assertThat(actual.length()).isEqualTo(1); - - JSONObject ban = actual.getJSONObject(0); - assertThat(ban.getInt("userId")).isEqualTo(UserHandle.getUserId(UID_P)); - assertThat(ban.getString("packageName")).isEqualTo(PKG_P); - } - - @Test public void testDumpBansJson_postPermissionMigration() throws Exception { // confirm that the package bans that are in the output include all packages that // have their permission set to false, and not based on PackagePreferences importance - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs - // package preferences: PKG_O not banned based on local importance, and PKG_P is - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); + mHelper.canShowBadge(PKG_O, UID_O); // expected output ArraySet<Pair<Integer, String>> expected = new ArraySet<>(); @@ -2967,10 +2852,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDumpBansJson_givenNullInput() throws Exception { // no one should do this, but... - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); JSONArray actual = mHelper.dumpBansJson( new NotificationManagerService.DumpFilter(), null); @@ -2978,59 +2859,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testDumpString_prePermissionMigration() { - // confirm that the string resulting from dumpImpl contains only info from package prefs - when(mPermissionHelper.isMigrationEnabled()).thenReturn(false); - - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); - appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs - appPermissions.put(new Pair(3, "third"), new Pair(false, true)); // not in local prefs - appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs - - // local package preferences: PKG_O is not banned even though the permissions would - // indicate so - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); - - // get dump output as a string so we can inspect the contents later - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - mHelper.dump(pw, "", new NotificationManagerService.DumpFilter(), appPermissions); - pw.flush(); - String actual = sw.toString(); - - // expected (substring) output for each preference - ArrayList<String> expected = new ArrayList<>(); - expected.add(PKG_O + " (" + UID_O + ") importance=HIGH"); - expected.add(PKG_P + " (" + UID_P + ") importance=NONE"); - - // make sure the things in app permissions do NOT show up - ArrayList<String> notExpected = new ArrayList<>(); - notExpected.add("first (1) importance=DEFAULT"); - notExpected.add("third (3) importance=NONE"); - notExpected.add("userSet="); // no user-set information pre migration - - for (String exp : expected) { - assertTrue(actual.contains(exp)); - } - - for (String notExp : notExpected) { - assertFalse(actual.contains(notExp)); - } - - // also make sure it works the same if we pass in a null input - StringWriter sw2 = new StringWriter(); - PrintWriter pw2 = new PrintWriter(sw2); - mHelper.dump(pw2, "", new NotificationManagerService.DumpFilter(), null); - pw.flush(); - String actualWithNullInput = sw2.toString(); - assertThat(actualWithNullInput).isEqualTo(actual); - } - - @Test public void testDumpString_postPermissionMigration() { // confirm that the string resulting from dumpImpl contains only importances from permission - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs @@ -3038,8 +2868,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs // local package preferences - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); + mHelper.canShowBadge(PKG_O, UID_O); + mHelper.canShowBadge(PKG_P, UID_P); // get dump output as a string so we can inspect the contents later StringWriter sw = new StringWriter(); @@ -3072,11 +2902,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDumpString_givenNullInput() { // test that this doesn't choke on null input - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); // local package preferences - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); + mHelper.canShowBadge(PKG_O, UID_O); + mHelper.canShowBadge(PKG_P, UID_P); // get dump output StringWriter sw = new StringWriter(); @@ -3090,48 +2919,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testDumpProto_prePermissionMigration() throws Exception { - // test that dumping to proto gets the importances from the right place - when(mPermissionHelper.isMigrationEnabled()).thenReturn(false); - - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); - appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs - appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs - appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs - - // local package preferences - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); - - // expected output: only the local preferences - // map format: (uid, package name) -> importance (int) - ArrayMap<Pair<Integer, String>, Integer> expected = new ArrayMap<>(); - expected.put(new Pair(UID_O, PKG_O), IMPORTANCE_HIGH); - expected.put(new Pair(UID_P, PKG_P), IMPORTANCE_NONE); - - // get the proto output and inspect its contents - ProtoOutputStream proto = new ProtoOutputStream(); - mHelper.dump(proto, new NotificationManagerService.DumpFilter(), appPermissions); - - RankingHelperProto actual = RankingHelperProto.parseFrom(proto.getBytes()); - assertThat(actual.records.length).isEqualTo(expected.size()); - for (int i = 0; i < actual.records.length; i++) { - RankingHelperProto.RecordProto record = actual.records[i]; - Pair<Integer, String> pkgKey = new Pair(record.uid, record.package_); - assertTrue(expected.containsKey(pkgKey)); - assertThat(record.importance).isEqualTo(expected.get(pkgKey)); - } - - // also check that it's the same as passing in null input - ProtoOutputStream proto2 = new ProtoOutputStream(); - mHelper.dump(proto2, new NotificationManagerService.DumpFilter(), null); - assertThat(proto.getBytes()).isEqualTo(proto2.getBytes()); - } - - @Test public void testDumpProto_postPermissionMigration() throws Exception { // test that dumping to proto gets the importances from the right place - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); // permissions -- these should take precedence ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); @@ -3140,8 +2929,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs // local package preferences - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_LOW); + mHelper.canShowBadge(PKG_O, UID_O); + mHelper.canShowBadge(PKG_P, UID_P); // expected output: all the packages, but only the ones provided via appPermissions // should have importance set (aka not PKG_P) @@ -3429,14 +3218,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testAppBlockedLogging() { - mHelper.setEnabled(PKG_N_MR1, 1020, false); - assertEquals(1, mLogger.getCalls().size()); - assertEquals( - NotificationChannelLogger.NotificationChannelEvent.APP_NOTIFICATIONS_BLOCKED, - mLogger.get(0).event); - } - @Test public void testXml_statusBarIcons_default() throws Exception { String preQXml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -3517,7 +3298,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testIsDelegateAllowed_noDelegate() { - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED); + mHelper.canShowBadge(PKG_O, UID_O); assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "whatever", 0)); } @@ -3555,7 +3336,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDelegateXml_noDelegate() throws Exception { - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED); + mHelper.canShowBadge(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, @@ -3744,318 +3525,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testLockChannelsForOEM_emptyList() { - mHelper.lockChannelsForOEM(null); - mHelper.lockChannelsForOEM(new String[0]); - // no exception - } - - @Test - public void testLockChannelsForOEM_appWide() { - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT); - // different uids, same package - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - mHelper.createNotificationChannel(PKG_O, 3, b, false, false); - mHelper.createNotificationChannel(PKG_O, 30, c, true, true); - - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_onlyGivenPkg() { - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false); - - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelSpecific() { - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT); - // different uids, same package - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - mHelper.createNotificationChannel(PKG_O, 3, b, false, false); - mHelper.createNotificationChannel(PKG_O, 30, c, true, true); - - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"}); - - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet() { - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false); - - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet() { - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"}); - - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT); - // different uids, same package - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - mHelper.createNotificationChannel(PKG_O, 3, b, false, false); - mHelper.createNotificationChannel(PKG_O, 30, c, true, true); - - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet_restoreData() - throws Exception { - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - final String xml = "<ranking version=\"1\">\n" - + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" - + "<channel id=\"a\" name=\"a\" importance=\"3\"/>" - + "<channel id=\"b\" name=\"b\" importance=\"3\"/>" - + "</package>" - + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 + "\" >\n" - + "<channel id=\"a\" name=\"a\" importance=\"3\"/>" - + "<channel id=\"b\" name=\"b\" importance=\"3\"/>" - + "</package>" - + "</ranking>"; - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), - null); - parser.nextTag(); - mHelper.readXml(parser, false, UserHandle.USER_ALL); - - assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, "a", false) - .isImportanceLockedByOEM()); - assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "b", false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet_restoreData_postMigration() - throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - final String xml = "<ranking version=\"1\">\n" - + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" - + "<channel id=\"a\" name=\"a\" importance=\"3\"/>" - + "<channel id=\"b\" name=\"b\" importance=\"3\"/>" - + "</package>" - + "</ranking>"; - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), - null); - parser.nextTag(); - mHelper.readXml(parser, false, UserHandle.USER_ALL); - - assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, "a", false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet_restoreData() - throws Exception { - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"}); - - final String xml = "<ranking version=\"1\">\n" - + "<package name=\"" + PKG_O + "\" uid=\"" + 3 + "\" >\n" - + "<channel id=\"a\" name=\"a\" importance=\"3\"/>" - + "<channel id=\"b\" name=\"b\" importance=\"3\"/>" - + "</package>" - + "<package name=\"" + PKG_O + "\" uid=\"" + 30 + "\" >\n" - + "<channel id=\"c\" name=\"c\" importance=\"3\"/>" - + "</package>" - + "</ranking>"; - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), - null); - parser.nextTag(); - mHelper.readXml(parser, false, UserHandle.USER_ALL); - - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "a", false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, "b", false) - .isImportanceLockedByOEM()); - assertTrue(mHelper.getNotificationChannel(PKG_O, 30, "c", false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet_restoreData_postMigration() - throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"}); - - final String xml = "<ranking version=\"1\">\n" - + "<package name=\"" + PKG_O + "\" uid=\"" + 3 + "\" >\n" - + "<channel id=\"a\" name=\"a\" importance=\"3\"/>" - + "<channel id=\"b\" name=\"b\" importance=\"3\"/>" - + "</package>" - + "<package name=\"" + PKG_O + "\" uid=\"" + 30 + "\" >\n" - + "<channel id=\"c\" name=\"c\" importance=\"3\"/>" - + "</package>" - + "</ranking>"; - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), - null); - parser.nextTag(); - mHelper.readXml(parser, false, UserHandle.USER_ALL); - - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "a", false) - .isImportanceLockedByOEM()); - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "b", false) - .isImportanceLockedByOEM()); - assertFalse(mHelper.getNotificationChannel(PKG_O, 30, "c", false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelSpecific_clearData() { - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - mHelper.getImportance(PKG_O, UID_O); - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":" + a.getId()}); - mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) - .isImportanceLockedByOEM()); - - mHelper.clearData(PKG_O, UID_O); - - // it's back! - mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - // and still locked - assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelDoesNotExistYet_appWide() { - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - - mHelper.createNotificationChannel(PKG_O, 3, b, true, false); - assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific() { - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"}); - - assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) - .isImportanceLockedByOEM()); - - mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false); - assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelSpecific_clearData_postMigration() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - mHelper.getImportance(PKG_O, UID_O); - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":" + a.getId()}); - mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) - .isImportanceLockedByOEM()); - - mHelper.clearData(PKG_O, UID_O); - - // it's back! - mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - // and never locked - assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelDoesNotExistYet_appWide_postMigration() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_O, 3, a, true, false); - - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false) - .isImportanceLockedByOEM()); - - mHelper.createNotificationChannel(PKG_O, 3, b, true, false); - assertFalse(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false) - .isImportanceLockedByOEM()); - } - - @Test - public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific_postMigration() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); - NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - - mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"}); - - assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) - .isImportanceLockedByOEM()); + public void testUpdateNotificationChannel_fixedPermission() { + List<UserInfo> users = ImmutableList.of(new UserInfo(UserHandle.USER_SYSTEM, "user0", 0)); + when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); + PackageInfo pm = new PackageInfo(); + pm.packageName = PKG_O; + pm.applicationInfo = new ApplicationInfo(); + pm.applicationInfo.uid = UID_O; + List<PackageInfo> packages = ImmutableList.of(pm); + when(mPm.getInstalledPackagesAsUser(any(), anyInt())).thenReturn(packages); + mHelper.updateFixedImportance(users); - mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false); - assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) - .isImportanceLockedByOEM()); - } + assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O)); - @Test - public void testUpdateNotificationChannel_oemLockedImportance() { NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); - mHelper.lockChannelsForOEM(new String[] {PKG_O}); - NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE); update.setAllowBubbles(false); @@ -4065,18 +3550,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance()); assertEquals(false, mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble()); - - mHelper.updateNotificationChannel(PKG_O, UID_O, update, true); - - assertEquals(IMPORTANCE_HIGH, - mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance()); } @Test - public void testUpdateNotificationChannel_fixedPermission() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); - + public void testUpdateNotificationChannel_defaultApp() { + ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>(); + toAdd.add(new Pair(PKG_O, UID_O)); + mHelper.updateDefaultApps(0, null, toAdd); NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); @@ -4093,7 +3573,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testUpdateNotificationChannel_fixedPermission_butUserPreviouslyBlockedIt() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_NONE); @@ -4112,7 +3591,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testUpdateNotificationChannel_fixedPermission_butAppAllowsIt() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); @@ -4132,7 +3610,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testUpdateNotificationChannel_notFixedPermission() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(false); NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); @@ -4150,6 +3627,58 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testUpdateFixedImportance_multiUser() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); + NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT); + // different uids, same package + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false); + mHelper.createNotificationChannel(PKG_O, UserHandle.PER_USER_RANGE + 1, c, true, true); + + UserInfo user = new UserInfo(); + user.id = 0; + List<UserInfo> users = ImmutableList.of(user); + when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); + PackageInfo pm = new PackageInfo(); + pm.packageName = PKG_O; + pm.applicationInfo = new ApplicationInfo(); + pm.applicationInfo.uid = UID_O; + List<PackageInfo> packages = ImmutableList.of(pm); + when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + mHelper.updateFixedImportance(users); + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertFalse(mHelper.getNotificationChannel( + PKG_O, UserHandle.PER_USER_RANGE + 1, c.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateFixedImportance_channelDoesNotExistYet() { + UserInfo user = new UserInfo(); + user.id = 0; + List<UserInfo> users = ImmutableList.of(user); + when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); + PackageInfo pm = new PackageInfo(); + pm.packageName = PKG_O; + pm.applicationInfo = new ApplicationInfo(); + pm.applicationInfo.uid = UID_O; + List<PackageInfo> packages = ImmutableList.of(pm); + when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + mHelper.updateFixedImportance(users); + + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test public void testUpdateDefaultApps_add_multiUser() { NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); @@ -4314,6 +3843,62 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testUpdateFixedImportance_thenDefaultAppsRemoves() { + UserInfo user = new UserInfo(); + user.id = 0; + List<UserInfo> users = ImmutableList.of(user); + when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(true); + PackageInfo pm = new PackageInfo(); + pm.packageName = PKG_O; + pm.applicationInfo = new ApplicationInfo(); + pm.applicationInfo.uid = UID_O; + List<PackageInfo> packages = ImmutableList.of(pm); + when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + mHelper.updateFixedImportance(users); + + ArraySet<String> toRemove = new ArraySet<>(); + toRemove.add(PKG_O); + mHelper.updateDefaultApps(0, toRemove, null); + + assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O)); + + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + + // Still locked by permission if not role + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateDefaultApps_thenNotFixedPermission() { + ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>(); + toAdd.add(new Pair(PKG_O, UID_O)); + mHelper.updateDefaultApps(0, null, toAdd); + + UserInfo user = new UserInfo(); + user.id = 0; + List<UserInfo> users = ImmutableList.of(user); + when(mPermissionHelper.isPermissionFixed(PKG_O, 0)).thenReturn(false); + PackageInfo pm = new PackageInfo(); + pm.packageName = PKG_O; + pm.applicationInfo = new ApplicationInfo(); + pm.applicationInfo.uid = UID_O; + List<PackageInfo> packages = ImmutableList.of(pm); + when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + mHelper.updateFixedImportance(users); + + assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O)); + + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + + // Still locked by role if not permission + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test public void testChannelXml_backupDefaultApp() throws Exception { NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); @@ -5312,56 +4897,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testPullPackagePreferencesStats_prePermissionMigration() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(false); - - // build a collection of app permissions that should be passed in but ignored - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); - appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs - appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs - appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs - - // package preferences: PKG_O not banned based on local importance, and PKG_P is - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); - - // expected output. format: uid -> importance, as only uid (and not package name) - // is in PackageNotificationPreferences - ArrayMap<Integer, Integer> expected = new ArrayMap<>(); - expected.put(UID_O, IMPORTANCE_HIGH); - expected.put(UID_P, IMPORTANCE_NONE); - - // unexpected output. these UIDs should not show up in the output at all - ArraySet<Integer> unexpected = new ArraySet<>(); - unexpected.add(1); - unexpected.add(3); - - ArrayList<StatsEvent> events = new ArrayList<>(); - mHelper.pullPackagePreferencesStats(events, appPermissions); - - for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { - if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) { - int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER); - - // this shouldn't be any of the forbidden uids - assertFalse(unexpected.contains(uid)); - - // if it's one of the expected ids, then make sure the importance matches - assertTrue(expected.containsKey(uid)); - assertThat(expected.get(uid)).isEqualTo( - builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER)); - - // pre-migration, the userSet field will always default to false - boolean userSet = builder.getBoolean( - PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER); - assertFalse(userSet); - } - } - } - - @Test public void testPullPackagePreferencesStats_postPermissionMigration() { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); // build a collection of app permissions that should be passed in but ignored ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); @@ -5369,9 +4905,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { appPermissions.put(new Pair(3, "third"), new Pair(false, true)); // not in local prefs appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, true)); // in local prefs - // package preferences: PKG_O not banned based on local importance, and PKG_P is - mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH); - mHelper.setImportance(PKG_P, UID_P, IMPORTANCE_NONE); + // local preferences + mHelper.canShowBadge(PKG_O, UID_O); + mHelper.canShowBadge(PKG_P, UID_P); // expected output. format: uid -> importance, as only uid (and not package name) // is in PackageNotificationPreferences @@ -5433,27 +4969,4 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue((channelB.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0); assertTrue((channelC.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0); } - - @Test - public void testDefaultChannelUpdatesApp_preMigrationToPermissions() throws Exception { - final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1, - UID_N_MR1, - NotificationChannel.DEFAULT_CHANNEL_ID, false); - defaultChannel.setImportance(IMPORTANCE_NONE); - mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true); - - assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_N_MR1, UID_N_MR1)); - } - - @Test - public void testDefaultChannelDoesNotUpdateApp_postMigrationToPermissions() throws Exception { - when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); - final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1, - UID_N_MR1, - NotificationChannel.DEFAULT_CHANNEL_ID, false); - defaultChannel.setImportance(IMPORTANCE_NONE); - mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true); - - assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1, UID_N_MR1)); - } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index 0bfd2020622f..98c156e6f3b5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -168,7 +168,8 @@ public class RoleObserverTest extends UiServiceTestCase { mock(StatsManager.class), mock(TelephonyManager.class), mock(ActivityManagerInternal.class), mock(MultiRateLimiter.class), mock(PermissionHelper.class), - mock(UsageStatsManagerInternal.class), mock (TelecomManager.class)); + mock(UsageStatsManagerInternal.class), mock (TelecomManager.class), + mock(NotificationChannelLogger.class)); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 533540e2568d..a34896a419ed 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -611,10 +611,9 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setRequestedOrientation(activityCurOrientation == ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE); - // Asserts fixed orientation request is ignored, and the orientation is not changed - // (fill Task). - assertEquals(activityCurOrientation, activity.getConfiguration().orientation); - assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio()); + // Asserts fixed orientation request is not ignored, and the orientation is changed. + assertNotEquals(activityCurOrientation, activity.getConfiguration().orientation); + assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio()); } @Test @@ -2854,6 +2853,11 @@ public class ActivityRecordTests extends WindowTestsBase { assertTrue(activity2.isResizeable()); activity1.reparent(taskFragment1, POSITION_TOP); + // Adds an Activity which doesn't have shared starting data, and verify if it blocks + // starting window removal. + final ActivityRecord activity3 = new ActivityBuilder(mAtm).build(); + taskFragment2.addChild(activity3, POSITION_TOP); + verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl), eq(task.mSurfaceControl)); assertEquals(activity1.mStartingData, startingWindow.mStartingData); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index eb6395b46120..35921585d56f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -39,8 +39,10 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; @@ -48,7 +50,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import android.graphics.Rect; import android.os.Binder; @@ -376,7 +380,7 @@ public class AppTransitionTests extends WindowTestsBase { doReturn(false).when(dc).onDescendantOrientationChanged(any()); final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "exiting app"); - final ActivityRecord exitingActivity= exitingAppWindow.mActivityRecord; + final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord; // Wait until everything in animation handler get executed to prevent the exiting window // from being removed during WindowSurfacePlacer Traversal. waitUntilHandlersIdle(); @@ -405,6 +409,41 @@ public class AppTransitionTests extends WindowTestsBase { } @Test + public void testDelayWhileRecents() { + final DisplayContent dc = createNewDisplay(Display.STATE_ON); + doReturn(false).when(dc).onDescendantOrientationChanged(any()); + final Task task = createTask(dc); + + // Simulate activity1 launches activity2. + final ActivityRecord activity1 = createActivityRecord(task); + activity1.setVisible(true); + activity1.mVisibleRequested = false; + activity1.allDrawn = true; + final ActivityRecord activity2 = createActivityRecord(task); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + activity2.allDrawn = true; + + dc.mClosingApps.add(activity1); + dc.mOpeningApps.add(activity2); + dc.prepareAppTransition(TRANSIT_OPEN); + assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN)); + + // Wait until everything in animation handler get executed to prevent the exiting window + // from being removed during WindowSurfacePlacer Traversal. + waitUntilHandlersIdle(); + + // Start recents + doReturn(true).when(task) + .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS)); + + dc.mAppTransitionController.handleAppTransitionReady(); + + verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean()); + verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean()); + } + + @Test public void testGetAnimationStyleResId() { // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without // specifying window type. diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index 9fc9489e3c2e..1d14dc31fa26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -82,6 +82,7 @@ import android.util.Pair; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.telephony.CellBroadcastUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; @@ -297,6 +298,22 @@ public class LockTaskControllerTest { } @Test + public void testLockTaskViolation_wirelessEmergencyAlerts() { + // GIVEN one task record with allowlisted auth that is in lock task mode + Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // GIVEN cellbroadcast task necessary for emergency warning alerts + Task cellbroadcastreceiver = getTask( + new Intent().setComponent( + CellBroadcastUtils.getDefaultCellBroadcastAlertDialogComponent(mContext)), + LOCK_TASK_AUTH_PINNABLE); + + // THEN the cellbroadcast task should all be allowed + assertFalse(mLockTaskController.isLockTaskModeViolation(cellbroadcastreceiver)); + } + + @Test public void testStopLockTaskMode() throws Exception { // GIVEN one task record with allowlisted auth that is in lock task mode Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED); 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 76fb7ff2e6f8..35d8129eb385 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1876,6 +1876,28 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testResizableFixedOrientationAppInSplitScreen_letterboxForDifferentOrientation() { + setUpDisplaySizeWithApp(1000, 2800); + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Resizable landscape-only activity. + prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_LANDSCAPE, /* isUnresizable= */ false); + + final Rect originalBounds = new Rect(mActivity.getBounds()); + + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, 1000, 1400); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); + + // Resizable activity is not in size compat mode but in the letterbox for fixed orientation. + assertFitted(); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + } + + @Test public void testSupportsNonResizableInSplitScreen_fillTaskForSameOrientation() { // Support non resizable in multi window mAtm.mDevEnableNonResizableMultiWindow = true; diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java index 177e819878c7..2893f807c8a8 100644 --- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java @@ -23,6 +23,7 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbRequest; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceStatus; @@ -44,6 +45,7 @@ import libcore.io.IoUtils; import java.io.Closeable; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.ArrayList; /** @@ -73,10 +75,10 @@ public final class UsbDirectMidiDevice implements Closeable { // event schedulers for each input port of the physical device private MidiEventScheduler[] mEventSchedulers; - // Arbitrary number for timeout to not continue sending/receiving number from - // an inactive device. This number tries to balances the number of cycles and - // not being permanently stuck. - private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 100; + // Arbitrary number for timeout to not continue sending to + // an inactive device. This number tries to balances the number + // of cycles and not being permanently stuck. + private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 10; private ArrayList<UsbDeviceConnection> mUsbDeviceConnections; private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints; @@ -330,48 +332,55 @@ public final class UsbDirectMidiDevice implements Closeable { new Thread("UsbDirectMidiDevice input thread " + portFinal) { @Override public void run() { - byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()]; - Log.d(TAG, "input buffer size: " + inputBuffer.length); + final UsbRequest request = new UsbRequest(); try { + request.initialize(connectionFinal, endpointFinal); + byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()]; while (true) { // Record time of event immediately after waking. long timestamp = System.nanoTime(); - synchronized (mLock) { - if (!mIsOpen) break; - - int nRead = connectionFinal.bulkTransfer(endpointFinal, - inputBuffer, inputBuffer.length, - BULK_TRANSFER_TIMEOUT_MILLISECONDS); - - if (nRead > 0) { - if (DEBUG) { - logByteArray("Input before conversion ", inputBuffer, - 0, nRead); - } - byte[] convertedArray; - if (mIsUniversalMidiDevice) { - // For USB, each 32 bit word of a UMP is - // sent with the least significant byte first. - convertedArray = swapEndiannessPerWord(inputBuffer, - nRead); - } else { - convertedArray = - mUsbMidiPacketConverter.usbMidiToRawMidi( - inputBuffer, nRead); - } - - if (DEBUG) { - logByteArray("Input after conversion ", convertedArray, - 0, convertedArray.length); - } - - outputReceivers[portFinal].send(convertedArray, 0, - convertedArray.length, timestamp); + if (!mIsOpen) break; + final ByteBuffer byteBuffer = ByteBuffer.wrap(inputBuffer); + if (!request.queue(byteBuffer)) { + Log.w(TAG, "Cannot queue request"); + break; + } + final UsbRequest response = connectionFinal.requestWait(); + if (response != request) { + Log.w(TAG, "Unexpected response"); + continue; + } + int bytesRead = byteBuffer.position(); + + if (bytesRead > 0) { + if (DEBUG) { + logByteArray("Input before conversion ", inputBuffer, + 0, bytesRead); + } + byte[] convertedArray; + if (mIsUniversalMidiDevice) { + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + convertedArray = swapEndiannessPerWord(inputBuffer, + bytesRead); + } else { + convertedArray = + mUsbMidiPacketConverter.usbMidiToRawMidi( + inputBuffer, bytesRead); } + + if (DEBUG) { + logByteArray("Input after conversion ", convertedArray, + 0, convertedArray.length); + } + outputReceivers[portFinal].send(convertedArray, 0, + convertedArray.length, timestamp); } } } catch (IOException e) { Log.d(TAG, "reader thread exiting"); + } finally { + request.close(); } Log.d(TAG, "input thread exit"); } @@ -564,6 +573,10 @@ public final class UsbDirectMidiDevice implements Closeable { Log.e(TAG, "Usb Interface is null"); return false; } + if (connection == null) { + Log.e(TAG, "UsbDeviceConnection is null"); + return false; + } if (!connection.claimInterface(usbInterface, true)) { Log.e(TAG, "Can't claim interface"); return false; diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index 85d59a216f25..9dfb0cc289ee 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -361,7 +361,10 @@ public final class LocationAccessPolicy { return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid); } - private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { + /** + * @return Whether location is enabled for the given user. + */ + public static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { LocationManager locationManager = context.getSystemService(LocationManager.class); if (locationManager == null) { Log.w(TAG, "Couldn't get location manager, denying location access"); @@ -370,6 +373,14 @@ public final class LocationAccessPolicy { return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); } + /** + * @return An array of packages that are always allowed to access location. + */ + public static @NonNull String[] getLocationBypassPackages(@NonNull Context context) { + return context.getResources().getStringArray( + com.android.internal.R.array.config_serviceStateLocationAllowedPackages); + } + private static boolean checkInteractAcrossUsersFull( @NonNull Context context, int pid, int uid) { return checkManifestPermission(context, pid, uid, diff --git a/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java index 6c6375586225..6181329bcb2a 100644 --- a/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java +++ b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java @@ -16,6 +16,7 @@ package com.android.internal.telephony; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -62,4 +63,17 @@ public class CellBroadcastUtils { return packageName; } + + /** + * Utility method to get cellbroadcast alert dialog component name + */ + public static ComponentName getDefaultCellBroadcastAlertDialogComponent(Context context) { + String cellBroadcastReceiverPackageName = + getDefaultCellBroadcastReceiverPackageName(context); + if (TextUtils.isEmpty(cellBroadcastReceiverPackageName)) { + return null; + } + return ComponentName.createRelative(cellBroadcastReceiverPackageName, + "com.android.cellbroadcastreceiver.CellBroadcastAlertDialog"); + } } diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 78b0b844865d..4924a82c385f 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -68,7 +68,6 @@ import java.util.stream.Collectors; public final class SmsApplication { static final String LOG_TAG = "SmsApplication"; public static final String PHONE_PACKAGE_NAME = "com.android.phone"; - public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services"; public static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service"; public static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony"; @@ -541,11 +540,13 @@ public final class SmsApplication { PackageManager packageManager = context.getPackageManager(); AppOpsManager appOps = context.getSystemService(AppOpsManager.class); + final String bluetoothPackageName = context.getResources() + .getString(com.android.internal.R.string.config_systemBluetoothStack); // Assign permission to special system apps assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, PHONE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, - BLUETOOTH_PACKAGE_NAME, true); + bluetoothPackageName, false); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, MMS_SERVICE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, @@ -1128,8 +1129,11 @@ public final class SmsApplication { return false; } final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context); + final String bluetoothPackageName = context.getResources() + .getString(com.android.internal.R.string.config_systemBluetoothStack); + if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName)) - || BLUETOOTH_PACKAGE_NAME.equals(packageName)) { + || bluetoothPackageName.equals(packageName)) { return true; } return false; diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index f47cf3384791..e7d95e4f53b3 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -16,6 +16,8 @@ package android.telephony; +import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; + import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_ANOMALY_DETECTED; import android.annotation.NonNull; @@ -73,6 +75,7 @@ public final class AnomalyReporter { * * This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is * system protected. Invoking this method unless you are the system will result in an error. + * Carrier Id will be set as UNKNOWN_CARRIER_ID. * * @param eventId a fixed event ID that will be sent for each instance of the same event. This * ID should be generated randomly. @@ -81,6 +84,23 @@ public final class AnomalyReporter { * static and must not contain any sensitive information (especially PII). */ public static void reportAnomaly(@NonNull UUID eventId, String description) { + reportAnomaly(eventId, description, UNKNOWN_CARRIER_ID); + } + + /** + * If enabled, build and send an intent to a Debug Service for logging. + * + * This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is + * system protected. Invoking this method unless you are the system will result in an error. + * + * @param eventId a fixed event ID that will be sent for each instance of the same event. This + * ID should be generated randomly. + * @param description an optional description, that if included will be used as the subject for + * identification and discussion of this event. This description should ideally be + * static and must not contain any sensitive information (especially PII). + * @param carrierId the carrier of the id associated with this event. + */ + public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) { if (sContext == null) { Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId); return; @@ -88,7 +108,7 @@ public final class AnomalyReporter { TelephonyStatsLog.write( TELEPHONY_ANOMALY_DETECTED, - 0, // TODO: carrier id needs to be populated + carrierId, eventId.getLeastSignificantBits(), eventId.getMostSignificantBits()); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index d07d8097bce4..2337a5aae467 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3426,10 +3426,21 @@ public class SubscriptionManager { * Get subscriptionInfo list of subscriptions that are in the same group of given subId. * See {@link #createSubscriptionGroup(List)} for more details. * - * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE} - * permission or had carrier privilege permission on the subscription. + * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE} + * or carrier privilege permission on the subscription. * {@link TelephonyManager#hasCarrierPrivileges()} * + * <p>Starting with API level 33, the caller needs the additional permission + * {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER} + * to get the list of subscriptions associated with a group UUID. + * This method can be invoked if one of the following requirements is met: + * <ul> + * <li>If the app has carrier privilege permission. + * {@link TelephonyManager#hasCarrierPrivileges()} + * <li>If the app has {@link android.Manifest.permission#READ_PHONE_STATE} and + * {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER} permission. + * </ul> + * * @throws IllegalStateException if Telephony service is in bad state. * @throws SecurityException if the caller doesn't meet the requirements * outlined above. diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 843827befb65..186241217588 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -118,8 +118,10 @@ public class RcsFeature extends ImsFeature { @Override public void setCapabilityExchangeEventListener( @Nullable ICapabilityExchangeEventListener listener) throws RemoteException { - CapabilityExchangeEventListener listenerWrapper = - new CapabilityExchangeAidlWrapper(listener); + // Set the listener wrapper to null if the listener passed in is null. This will notify + // the RcsFeature to trigger the destruction of active capability exchange interface. + CapabilityExchangeEventListener listenerWrapper = listener != null + ? new CapabilityExchangeAidlWrapper(listener) : null; executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener(listenerWrapper), "setCapabilityExchangeEventListener"); } diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java index 7ee19fb37244..052ce3a902c1 100644 --- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java +++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; @@ -214,7 +215,7 @@ public class SmsApplicationTest { ApplicationInfo bluetoothApplicationInfo = new ApplicationInfo(); bluetoothApplicationInfo.uid = FAKE_BT_UID; bluetoothPackageInfo.applicationInfo = bluetoothApplicationInfo; - when(mPackageManager.getPackageInfo(eq(SmsApplication.BLUETOOTH_PACKAGE_NAME), anyInt())) + when(mPackageManager.getPackageInfo(matches(".*android.bluetooth.services"), anyInt())) .thenReturn(bluetoothPackageInfo); PackageInfo telephonyProviderPackageInfo = new PackageInfo(); |