diff options
204 files changed, 4351 insertions, 2866 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index dd0d07f68547..c3fc7b16ebdf 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -16,8 +16,10 @@ package android.app.tare; +import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; +import android.util.Log; /** * Provides access to the resource economy service. @@ -26,6 +28,69 @@ import android.content.Context; */ @SystemService(Context.RESOURCE_ECONOMY_SERVICE) public class EconomyManager { + private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName(); + + /** + * 1 ARC = 1 GIGA-CAKE! + * + * @hide + */ + public static final long CAKE_IN_ARC = 1_000_000_000L; + + /** @hide */ + public static long arcToCake(int arcs) { + return arcs * CAKE_IN_ARC; + } + + /** + * Parses a configuration string to get the value in cakes. + * + * @hide + */ + public static long parseCreditValue(@Nullable final String val, final long defaultValCakes) { + String trunc; + if (val == null || (trunc = val.trim()).isEmpty()) { + return defaultValCakes; + } + long multiplier; + if (trunc.endsWith("c")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier = 1; + } else if (trunc.endsWith("ck")) { + trunc = trunc.substring(0, trunc.length() - 2); + multiplier = 1; + } else if (trunc.endsWith("A")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier = CAKE_IN_ARC; + } else if (trunc.endsWith("ARC")) { + trunc = trunc.substring(0, trunc.length() - 3); + multiplier = CAKE_IN_ARC; + } else { + // Don't risk using the wrong units + Log.e(TAG, "Couldn't determine units of credit value: " + val); + return defaultValCakes; + } + + // Allow people to shorten notation (eg. Mc for Megacake). + if (trunc.endsWith("k")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier *= 1_000; + } else if (trunc.endsWith("M")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier *= 1_000_000; + } else if (trunc.endsWith("G")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier *= 1_000_000_000; + } + + try { + return Long.parseLong(trunc) * multiplier; + } catch (NumberFormatException e) { + Log.e(TAG, "Malformed config string: " + val + " to " + trunc, e); + return defaultValCakes; + } + } + // Keys for AlarmManager TARE factors /** @hide */ public static final String KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED = @@ -276,179 +341,201 @@ public class EconomyManager { // Default values AlarmManager factors /** @hide */ - public static final int DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED = 500; + public static final long DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 200; + public static final long DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES = + arcToCake(256); /** @hide */ - public static final int DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP = 160; + public static final long DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES = arcToCake(160); /** @hide */ - public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440; + public static final long DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES = arcToCake(960); /** @hide */ - public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 4000; + public static final long DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(2880); /** @hide */ - public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 28_800; + public static final long DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000); // TODO: add AlarmManager modifier default values /** @hide */ - public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0; + public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0); /** @hide */ - public static final float DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING = 0.01f; + // 10 megacakes = .01 ARC + public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES = 10_000_000; /** @hide */ - public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX = 500; + public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT = 3; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX = 60; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES = arcToCake(60); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_INSTANT = 5; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_INSTANT_CAKES = + arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_ONGOING_CAKES = + arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_MAX = 500; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_MAX_CAKES = + arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT = 5; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES = + arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES = + arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX = 500; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT = 10; + public static final long DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX = 500; + public static final long DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT = 10; + public static final long DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX = 500; + public static final long DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES = + arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES = + arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES = + arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES = + arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP = 5; + public static final long DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES = arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE = 5; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE = 4; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE = 4; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE = 3; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE = 3; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE = 2; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(2); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE = - 2; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(2); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE = 1; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE = 10; + public static final long DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES = arcToCake(10); // Default values JobScheduler factors // TODO: add time_since_usage variable to min satiated balance factors /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 20000; + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES = arcToCake(15000); /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 10000; + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES = + arcToCake(7500); /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP = 2000; + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES = arcToCake(2000); /** @hide */ - public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000; + public static final long DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES = arcToCake(60000); /** @hide */ - public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 100_000; + public static final long DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(29_000); /** @hide */ - public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 460_000; + // TODO: set hard limit based on device type (phone vs tablet vs etc) + battery size + public static final long DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000); // TODO: add JobScheduler modifier default values /** @hide */ - public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0; + public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0); /** @hide */ - public static final float DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING = 0.5f; + public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES = CAKE_IN_ARC / 2; /** @hide */ - public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX = 15000; + public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES = arcToCake(15000); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT = 1; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX = 10; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT = 5; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES = + arcToCake(5); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES = + arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX = 5000; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES = arcToCake(5000); /** @hide */ - public static final int DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT = 10; + public static final long DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX = 5000; + public static final long DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES = arcToCake(5000); /** @hide */ - public static final int DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT = 10; + public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX = 5000; + public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(5000); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP = 30; + public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES = arcToCake(30); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE = 10; + public static final long DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE = 5; + public static final long DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES = arcToCake(5); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE = 8; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES = arcToCake(8); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE = 4; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE = 6; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES = arcToCake(6); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE = 3; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE = 4; + public static final long DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE = 2; + public static final long DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE = 2; + public static final long DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE = 1; + public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE = 60; + public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60); } 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 0e4887d27b53..2ea8592e883e 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2637,15 +2637,18 @@ public class AlarmManagerService extends SystemService { * Returns true if the given uid can set window to be as small as it wants. */ boolean isExemptFromMinWindowRestrictions(int uid) { - return isExemptFromExactAlarmPermission(uid); + return isExemptFromExactAlarmPermissionNoLock(uid); } /** * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, * allow-while-idle alarms. - * Note: It is ok to call this method without the lock {@link #mLock} held. + * <b> Note: This should not be called with {@link #mLock} held.</b> */ - boolean isExemptFromExactAlarmPermission(int uid) { + boolean isExemptFromExactAlarmPermissionNoLock(int uid) { + if (Build.IS_DEBUGGABLE && Thread.holdsLock(mLock)) { + Slog.wtfStack(TAG, "Alarm lock held while calling into DeviceIdleController"); + } return (UserHandle.isSameApp(mSystemUiUid, uid) || UserHandle.isCore(uid) || mLocalDeviceIdleController == null @@ -2747,7 +2750,7 @@ public class AlarmManagerService extends SystemService { } if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid) && !hasUseExactAlarmInternal(callingPackage, callingUid)) { - if (!isExemptFromExactAlarmPermission(callingUid)) { + if (!isExemptFromExactAlarmPermissionNoLock(callingUid)) { final String errorMessage = "Caller " + callingPackage + " needs to hold " + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set " + "exact alarms."; @@ -2810,7 +2813,7 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(packageName, userId)) { return true; } - return isExemptFromExactAlarmPermission(packageUid) + return isExemptFromExactAlarmPermissionNoLock(packageUid) || hasScheduleExactAlarmInternal(packageName, packageUid) || hasUseExactAlarmInternal(packageName, packageUid); } @@ -3862,10 +3865,7 @@ public class AlarmManagerService extends SystemService { // added: true => package was added to the deny list // added: false => package was removed from the deny list if (added) { - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, - changedPackage, /*killUid = */ true); - } + removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true); } else { sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); } @@ -3880,9 +3880,8 @@ public class AlarmManagerService extends SystemService { * * This is not expected to get called frequently. */ - @GuardedBy("mLock") - void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName, boolean killUid) { - if (isExemptFromExactAlarmPermission(uid) + void removeExactAlarmsOnPermissionRevoked(int uid, String packageName, boolean killUid) { + if (isExemptFromExactAlarmPermissionNoLock(uid) || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) { return; } @@ -3891,7 +3890,9 @@ public class AlarmManagerService extends SystemService { final Predicate<Alarm> whichAlarms = a -> (a.uid == uid && a.packageName.equals(packageName) && a.windowLength == 0); - removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); + synchronized (mLock) { + removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); + } if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), @@ -4807,10 +4808,7 @@ public class AlarmManagerService extends SystemService { case REMOVE_EXACT_ALARMS: int uid = msg.arg1; String packageName = (String) msg.obj; - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, packageName, /*killUid = */ - true); - } + removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true); break; case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true); @@ -4826,10 +4824,7 @@ public class AlarmManagerService extends SystemService { uid = msg.arg1; if (!hasScheduleExactAlarmInternal(packageName, uid) && !hasUseExactAlarmInternal(packageName, uid)) { - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, - packageName, /*killUid = */false); - } + removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */false); } break; case CHECK_EXACT_ALARM_PERMISSION_ON_FEATURE_TOGGLE: @@ -4849,10 +4844,7 @@ public class AlarmManagerService extends SystemService { if (defaultDenied) { if (!hasScheduleExactAlarmInternal(pkg, uid) && !hasUseExactAlarmInternal(pkg, uid)) { - synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, pkg, - true); - } + removeExactAlarmsOnPermissionRevoked(uid, pkg, true); } } else if (hasScheduleExactAlarmInternal(pkg, uid)) { sendScheduleExactAlarmPermissionStateChangedBroadcast(pkg, diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index c2e81882eed2..d0f719b13b89 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -16,43 +16,43 @@ package com.android.server.tare; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE; -import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED; -import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE; @@ -97,12 +97,12 @@ import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE; -import static com.android.server.tare.TareUtils.arcToCake; import static com.android.server.tare.TareUtils.cakeToString; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; @@ -157,14 +157,15 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { AlarmManagerEconomicPolicy(InternalResourceService irs) { super(irs); mInternalResourceService = irs; - loadConstants(""); + loadConstants("", null); } @Override - void setup() { - super.setup(); + void setup(@NonNull DeviceConfig.Properties properties) { + super.setup(properties); ContentResolver resolver = mInternalResourceService.getContext().getContentResolver(); - loadConstants(Settings.Global.getString(resolver, TARE_ALARM_MANAGER_CONSTANTS)); + loadConstants(Settings.Global.getString(resolver, TARE_ALARM_MANAGER_CONSTANTS), + properties); } @Override @@ -178,6 +179,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { @Override long getMaxSatiatedBalance() { + // TODO(230501287): adjust balance based on whether the app has the SCHEDULE_EXACT_ALARM + // permission granted. Apps without the permission granted shouldn't need a high balance + // since they won't be able to use exact alarms. Apps with the permission granted could + // have a higher balance, or perhaps just those with the USE_EXACT_ALARM permission since + // that is limited to specific use cases. return mMaxSatiatedBalance; } @@ -209,7 +215,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { return mRewards.get(rewardId); } - private void loadConstants(String policyValuesString) { + private void loadConstants(String policyValuesString, + @Nullable DeviceConfig.Properties properties) { mActions.clear(); mRewards.clear(); @@ -219,145 +226,159 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceExempted = arcToCake(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, - DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED)); - mMinSatiatedBalanceOther = arcToCake(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, - DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP)); - mMaxSatiatedBalance = arcToCake(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE, - DEFAULT_AM_MAX_SATIATED_BALANCE)); - mInitialSatiatedConsumptionLimit = arcToCake(mParser.getInt( - KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT)); + mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES); + mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, + KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, + DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + mMaxSatiatedBalance = getConstantAsCake(mParser, properties, + KEY_AM_MAX_SATIATED_BALANCE, + DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES); + mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES); mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, - arcToCake(mParser.getInt( - KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT))); + getConstantAsCake(mParser, properties, + KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES)); - final long exactAllowWhileIdleWakeupBasePrice = arcToCake( - mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE)); + final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP)), + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES), exactAllowWhileIdleWakeupBasePrice)); mActions.put(ACTION_ALARM_WAKEUP_EXACT, new Action(ACTION_ALARM_WAKEUP_EXACT, - arcToCake(mParser.getInt(KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP)), - arcToCake(mParser.getInt(KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP, + DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES))); final long inexactAllowWhileIdleWakeupBasePrice = - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE)); + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP)), + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES), inexactAllowWhileIdleWakeupBasePrice)); mActions.put(ACTION_ALARM_WAKEUP_INEXACT, new Action(ACTION_ALARM_WAKEUP_INEXACT, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE)))); - - final long exactAllowWhileIdleNonWakeupBasePrice = - arcToCake(mParser.getInt( - KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE)); + DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES))); + final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP)), + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES), exactAllowWhileIdleNonWakeupBasePrice)); + mActions.put(ACTION_ALARM_NONWAKEUP_EXACT, new Action(ACTION_ALARM_NONWAKEUP_EXACT, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE)))); - - final long inexactAllowWhileIdleNonWakeupBasePrice = - arcToCake(mParser.getInt( - KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE)); - + DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES))); + + final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES); + final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES); mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( - KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP)), + inexactAllowWhileIdleNonWakeupCtp, inexactAllowWhileIdleNonWakeupBasePrice)); + mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT, new Action(ACTION_ALARM_NONWAKEUP_INEXACT, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE)))); + DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES))); mActions.put(ACTION_ALARM_CLOCK, new Action(ACTION_ALARM_CLOCK, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP, - DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE)))); + DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES))); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, - arcToCake(mParser.getInt(KEY_AM_REWARD_TOP_ACTIVITY_INSTANT, - DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT)), - (long) (arcToCake(1) * mParser.getFloat(KEY_AM_REWARD_TOP_ACTIVITY_ONGOING, - DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING)), - arcToCake(mParser.getInt(KEY_AM_REWARD_TOP_ACTIVITY_MAX, - DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX)))); + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_TOP_ACTIVITY_INSTANT, + DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_TOP_ACTIVITY_ONGOING, + DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_TOP_ACTIVITY_MAX, + DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN, - arcToCake(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT, - DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT)), - arcToCake(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING, - DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING)), - arcToCake(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_MAX, - DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX)))); + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT, + DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING, + DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_NOTIFICATION_SEEN_MAX, + DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_INTERACTION, new Reward(REWARD_NOTIFICATION_INTERACTION, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT, - DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING, - DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX, - DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX)))); + DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION, - arcToCake(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT, - DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT)), - arcToCake(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING, - DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING)), - arcToCake(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_MAX, - DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX)))); + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT, + DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING, + DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_WIDGET_INTERACTION_MAX, + DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_OTHER_USER_INTERACTION, new Reward(REWARD_OTHER_USER_INTERACTION, - arcToCake(mParser.getInt(KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT, - DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT, + DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING, - DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX, - DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX)))); + DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES))); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java index 2109a8575777..c3eb5bf51161 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java @@ -18,6 +18,7 @@ package com.android.server.tare; import android.annotation.NonNull; import android.annotation.Nullable; +import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.SparseArray; @@ -57,10 +58,10 @@ public class CompleteEconomicPolicy extends EconomicPolicy { } @Override - void setup() { - super.setup(); + void setup(@NonNull DeviceConfig.Properties properties) { + super.setup(properties); for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { - mEnabledEconomicPolicies.valueAt(i).setup(); + mEnabledEconomicPolicies.valueAt(i).setup(properties); } updateMaxBalances(); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index 3a26aae217c2..d401373c0066 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -16,6 +16,8 @@ package com.android.server.tare; +import static android.app.tare.EconomyManager.parseCreditValue; + import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; @@ -27,7 +29,9 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; +import android.util.KeyValueListParser; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -170,7 +174,7 @@ public abstract class EconomicPolicy { } @CallSuper - void setup() { + void setup(@NonNull DeviceConfig.Properties properties) { for (int i = 0; i < NUM_COST_MODIFIERS; ++i) { final Modifier modifier = COST_MODIFIER_BY_INDEX[i]; if (modifier != null) { @@ -409,6 +413,22 @@ public abstract class EconomicPolicy { return "UNKNOWN_REWARD:" + Integer.toHexString(eventId); } + protected long getConstantAsCake(@NonNull KeyValueListParser parser, + @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) { + // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig + // config can cause issues since the scales may be different, so use one or the other. + if (parser.size() > 0) { + // User settings take precedence. Just stick with the Settings constants, even if there + // are invalid values. It's not worth the time to evaluate all the key/value pairs to + // make sure there are valid ones before deciding. + return parseCreditValue(parser.getString(key, null), defaultValCake); + } + if (properties != null) { + return parseCreditValue(properties.getString(key, null), defaultValCake); + } + return defaultValCake; + } + protected static void dumpActiveModifiers(IndentingPrintWriter pw) { for (int i = 0; i < NUM_COST_MODIFIERS; ++i) { pw.print("Modifier "); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index ce4604f80f50..2118eeb45d70 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -731,7 +731,7 @@ public class InternalResourceService extends SystemService { registerListeners(); mCurrentBatteryLevel = getCurrentBatteryLevel(); mHandler.post(this::setupHeavyWork); - mCompleteEconomicPolicy.setup(); + mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties()); } } @@ -1014,10 +1014,17 @@ public class InternalResourceService extends SystemService { Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS), false, this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS), false, this); - onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TARE)); + onPropertiesChanged(getAllDeviceConfigProperties()); updateEnabledStatus(); } + @NonNull + DeviceConfig.Properties getAllDeviceConfigProperties() { + // Don't want to cache the Properties object locally in case it ends up being large, + // especially since it'll only be used once/infrequently (during setup or on a change). + return DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TARE); + } + @Override public void onChange(boolean selfChange, Uri uri) { if (uri.equals(Settings.Global.getUriFor(Settings.Global.ENABLE_TARE))) { @@ -1030,6 +1037,7 @@ public class InternalResourceService extends SystemService { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { + boolean economicPolicyUpdated = false; synchronized (mLock) { for (String name : properties.getKeyset()) { if (name == null) { @@ -1039,6 +1047,12 @@ public class InternalResourceService extends SystemService { case KEY_DC_ENABLE_TARE: updateEnabledStatus(); break; + default: + if (!economicPolicyUpdated + && (name.startsWith("am") || name.startsWith("js"))) { + updateEconomicPolicy(); + economicPolicyUpdated = true; + } } } } @@ -1072,7 +1086,7 @@ public class InternalResourceService extends SystemService { mCompleteEconomicPolicy.tearDown(); mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this); if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) { - mCompleteEconomicPolicy.setup(); + mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties()); if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() || hardLimit != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 99b93ce5c22c..948f0a71510c 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -16,48 +16,48 @@ package com.android.server.tare; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE; -import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED; -import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE; @@ -106,12 +106,12 @@ import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE; -import static com.android.server.tare.TareUtils.arcToCake; import static com.android.server.tare.TareUtils.cakeToString; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; @@ -159,14 +159,15 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { JobSchedulerEconomicPolicy(InternalResourceService irs) { super(irs); mInternalResourceService = irs; - loadConstants(""); + loadConstants("", null); } @Override - void setup() { - super.setup(); + void setup(@NonNull DeviceConfig.Properties properties) { + super.setup(properties); ContentResolver resolver = mInternalResourceService.getContext().getContentResolver(); - loadConstants(Settings.Global.getString(resolver, TARE_JOB_SCHEDULER_CONSTANTS)); + loadConstants(Settings.Global.getString(resolver, TARE_JOB_SCHEDULER_CONSTANTS), + properties); } @Override @@ -211,7 +212,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { return mRewards.get(rewardId); } - private void loadConstants(String policyValuesString) { + private void loadConstants(String policyValuesString, + @Nullable DeviceConfig.Properties properties) { mActions.clear(); mRewards.clear(); @@ -221,118 +223,153 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceExempted = arcToCake( - mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, - DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED)); - mMinSatiatedBalanceOther = arcToCake( - mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, - DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP)); - mMaxSatiatedBalance = arcToCake(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE, - DEFAULT_JS_MAX_SATIATED_BALANCE)); - mInitialSatiatedConsumptionLimit = arcToCake(mParser.getInt( - KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT)); + mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES); + mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, + KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, + DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + mMaxSatiatedBalance = getConstantAsCake(mParser, properties, + KEY_JS_MAX_SATIATED_BALANCE, + DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES); + mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_JS_INITIAL_CONSUMPTION_LIMIT, + DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES); mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, - arcToCake(mParser.getInt( - KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT))); + getConstantAsCake(mParser, properties, + KEY_JS_HARD_CONSUMPTION_LIMIT, + DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES)); mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP, - DEFAULT_JS_ACTION_JOB_MAX_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_START_CTP, + DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MAX_RUNNING, new Action(ACTION_JOB_MAX_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_HIGH_START, new Action(ACTION_JOB_HIGH_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_START_CTP, - DEFAULT_JS_ACTION_JOB_HIGH_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_START_CTP, + DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_HIGH_RUNNING, new Action(ACTION_JOB_HIGH_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_DEFAULT_START, new Action(ACTION_JOB_DEFAULT_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_START_CTP, - DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_START_CTP, + DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_DEFAULT_RUNNING, new Action(ACTION_JOB_DEFAULT_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_LOW_START, new Action(ACTION_JOB_LOW_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_START_CTP, - DEFAULT_JS_ACTION_JOB_LOW_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_START_CTP, + DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_LOW_RUNNING, new Action(ACTION_JOB_LOW_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MIN_START, new Action(ACTION_JOB_MIN_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_START_CTP, - DEFAULT_JS_ACTION_JOB_MIN_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_START_CTP, + DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MIN_RUNNING, new Action(ACTION_JOB_MIN_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_TIMEOUT, new Action(ACTION_JOB_TIMEOUT, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP, - DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP, + DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES))); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, - arcToCake(mParser.getInt(KEY_JS_REWARD_TOP_ACTIVITY_INSTANT, - DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT)), - (long) (arcToCake(1) * mParser.getFloat(KEY_JS_REWARD_TOP_ACTIVITY_ONGOING, - DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING)), - arcToCake(mParser.getInt(KEY_JS_REWARD_TOP_ACTIVITY_MAX, - DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX)))); + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_TOP_ACTIVITY_INSTANT, + DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_TOP_ACTIVITY_ONGOING, + DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_TOP_ACTIVITY_MAX, + DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN, - arcToCake(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT, - DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT)), - arcToCake(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING, - DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING)), - arcToCake(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_MAX, - DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX)))); + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT, + DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING, + DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_NOTIFICATION_SEEN_MAX, + DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_INTERACTION, new Reward(REWARD_NOTIFICATION_INTERACTION, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT, - DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING, - DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX, - DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX)))); + DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION, - arcToCake(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT, - DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT)), - arcToCake(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING, - DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING)), - arcToCake(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_MAX, - DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX)))); + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT, + DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING, + DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_WIDGET_INTERACTION_MAX, + DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_OTHER_USER_INTERACTION, new Reward(REWARD_OTHER_USER_INTERACTION, - arcToCake(mParser.getInt(KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT, - DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT, + DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING, - DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX, - DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX)))); + DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES))); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/tare/README.md b/apex/jobscheduler/service/java/com/android/server/tare/README.md index 72d506972dd1..e338ed1c6987 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/README.md +++ b/apex/jobscheduler/service/java/com/android/server/tare/README.md @@ -18,16 +18,17 @@ The key tenets of TARE are: In an ideal world, the system could be said to most efficiently allocate resources by maximizing its profits — by maximizing the aggregate sum of the difference between an action's price (that the app ends up paying) and the cost to produce by the system. This assumes that more important -actions have a higher price than less important actions. With this assumption, maximizing profits -implies that the system runs the most important work first and proceeds in decreasing order of -importance. Of course, that also means the system will not run anything where an app would pay less -for the action than the system's cost to produce that action. Some of this breaks down when we throw -TOP apps into the mix — TOP apps pay 0 for all actions, even though the CTP may be greater -than 0. This is to ensure ideal user experience for the app the user is actively interacting with. -Similar caveats exist for system-critical processes (such as the OS itself) and apps running -foreground services (since those could be critical to user experience, as is the case for media and -navigation apps). Excluding those caveats/special situations, maximizing profits of actions -performed by apps in the background should be the target. +actions have a higher price than less important actions and all actors have perfect information and +convey that information accurately. With these assumptions, maximizing profits implies that the +system runs the most important work first and proceeds in decreasing order of importance. Of course, +that also means the system will not run anything where an app would pay less for the action than the +system's cost to produce that action. Some of this breaks down when we throw TOP apps into the mix +— TOP apps pay 0 for all actions, even though the CTP may be greater than 0. This is to ensure +ideal user experience for the app the user is actively interacting with. Similar caveats exist for +system-critical processes (such as the OS itself) and apps running foreground services (since those +could be critical to user experience, as is the case for media and navigation apps). Excluding those +caveats/special situations, maximizing profits of actions performed by apps in the background should +be the target. To achieve the goal laid out by TARE, we use Android Resource Credits (ARCs for short) as the internal/representative currency of the system. @@ -101,11 +102,37 @@ Tare Improvement Proposal #1 (TIP1) separated allocation (to apps) from supply ( allowed apps to accrue credits as appropriate while still limiting the total number of credits consumed. +# Potential Future Changes + +These are some ideas for further changes. There's no guarantee that they'll be implemented. + +* Include additional components and policies for them. TARE may benefit from adding policies for + components such as broadcast dispatching, network traffic, location requests, and sensor usage. +* Have a separate "account" for critical/special actions. In other words, have two accounts for each + app, where one acts like a special savings account and is only allowed to be used for special + actions such as expedited job execution. The second account would have a lower maximum than the + main account, but would help to make sure that normal actions don't interfere too much with more + critical actions. +* Transferring credits from one app to another. For apps that rely on others for some pieces of + work, it may be beneficial to allow the requesting app to transfer, donate, or somehow make + available some of its own credits to the app doing the work in order to make sure the working app + has enough credits available to do the work. +* Formulate values based on device hardware. For example, adjust the consumption limit based on the + battery size, or the price and/or CTP of actions based on hardware efficiency. +* Price discovery via an auction system. Instead of just setting a fixed price that may be modified + by device and app states, let an app say how much it's willing to pay for a specific action and + then have a small auction when the system needs to decide which app to perform the action for + first or how much to charge the app. + # Definitions * ARC: Android Resource Credits are the "currency" units used as an abstraction layer over the real battery drain. They allow the system to standardize costs and prices across various devices. * Cake: A lie; also the smallest unit of an ARC (1 cake = one-billionth of an ARC = 1 nano-ARC). When the apps request to do something, we shall let them eat cake. -* NARC: The smallest unit of an ARC. A narc is 1 nano-ARC. +* Cost to produce (CTP): An economic term that refers to the total cost incurred by a business to + produce a specific quantity of a product or offer a service. In TARE's context, CTP is meant to be + the estimated cost t ohe system to accomplish a certain action. These "actions" are basically APIs + that apps use to get something done. So the idea is to define the base cost for an app to use a + specific API. * Satiated: used to refer to when the device is fully charged (at 100% battery level)
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java index 87db8637ddac..6b6984f6ac17 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java @@ -16,6 +16,8 @@ package com.android.server.tare; +import static android.app.tare.EconomyManager.CAKE_IN_ARC; + import android.annotation.NonNull; import android.annotation.SuppressLint; import android.util.IndentingPrintWriter; @@ -26,8 +28,6 @@ import java.text.SimpleDateFormat; import java.time.Clock; class TareUtils { - private static final long CAKE_IN_ARC = 1_000_000_000L; - @SuppressLint("SimpleDateFormat") private static final SimpleDateFormat sDumpDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -35,10 +35,6 @@ class TareUtils { @VisibleForTesting static Clock sSystemClock = Clock.systemUTC(); - static long arcToCake(int arcs) { - return arcs * CAKE_IN_ARC; - } - static void dumpTime(IndentingPrintWriter pw, long time) { pw.print(sDumpDateFormat.format(time)); } @@ -56,7 +52,7 @@ class TareUtils { if (cakes == 0) { return "0 ARCs"; } - final long sub = Math.abs(cakes) % CAKE_IN_ARC; + final long sub = cakes % CAKE_IN_ARC; final long arcs = cakeToArc(cakes); if (arcs == 0) { return sub == 1 @@ -65,11 +61,11 @@ class TareUtils { } StringBuilder sb = new StringBuilder(); sb.append(arcs); - if (sub > 0) { - sb.append(".").append(sub / (CAKE_IN_ARC / 1000)); + if (sub != 0) { + sb.append(".").append(String.format("%03d", Math.abs(sub) / (CAKE_IN_ARC / 1000))); } sb.append(" ARC"); - if (arcs != 1 || sub > 0) { + if (arcs != 1 || sub != 0) { sb.append("s"); } return sb.toString(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index e9c29b8aa0a5..c802d20a5a57 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -261,8 +261,10 @@ public class KeyguardManager { CharSequence title, CharSequence description, int userId, boolean disallowBiometricsIfPolicyExists) { Intent intent = this.createConfirmDeviceCredentialIntent(title, description, userId); - intent.putExtra(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, - disallowBiometricsIfPolicyExists); + if (intent != null) { + intent.putExtra(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, + disallowBiometricsIfPolicyExists); + } return intent; } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4d4a57db84be..44dc28d2b0fa 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1414,11 +1414,9 @@ public class PackageParser { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); final ParseResult<android.content.pm.SigningDetails> result; if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying; since the signature - // is not verified and some system apps can have their V2+ signatures stripped allow - // pulling the certs from the jar signature. + // systemDir APKs are already trusted, save time by not verifying result = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( - input, apkPath, SigningDetails.SignatureSchemeVersion.JAR); + input, apkPath, minSignatureScheme); } else { result = ApkSignatureVerifier.verify(input, apkPath, minSignatureScheme); } diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index cc62d5337c02..9cd201a1acbc 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -168,15 +168,6 @@ "name": "CtsIncrementalInstallHostTestCases" }, { - "name": "CtsInstallHostTestCases" - }, - { - "name": "CtsStagedInstallHostTestCases" - }, - { - "name": "CtsExtractNativeLibsHostTestCases" - }, - { "name": "CtsAppSecurityHostTestCases", "options": [ { @@ -188,34 +179,12 @@ ] }, { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.pm.PackageParserTest" - } - ] - }, - { - "name": "CtsRollbackManagerHostTestCases" - }, - { "name": "CtsContentTestCases", "options": [ { "include-filter": "android.content.cts.IntentFilterTest" } ] - }, - { - "name": "CtsAppEnumerationTestCases" - }, - { - "name": "PackageManagerServiceUnitTests", - "options": [ - { - "include-filter": "com.android.server.pm.test.verify.domain" - } - ] } ] } diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING index ba4a62cdbbf1..8a1982a339ea 100644 --- a/core/java/android/content/pm/verify/domain/TEST_MAPPING +++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING @@ -12,9 +12,6 @@ "name": "CtsDomainVerificationDeviceStandaloneTestCases" }, { - "name": "CtsDomainVerificationDeviceMultiUserTestCases" - }, - { "name": "CtsDomainVerificationHostTestCases" } ] diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index dc38db2134f4..69105016e0ea 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -152,6 +152,7 @@ final class NavigationBarController { private boolean mDrawLegacyNavigationBarBackground; private final Rect mTempRect = new Rect(); + private final int[] mTempPos = new int[2]; Impl(@NonNull InputMethodService inputMethodService) { mService = inputMethodService; @@ -259,21 +260,28 @@ final class NavigationBarController { switch (originalInsets.touchableInsets) { case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: if (inputFrame.getVisibility() == View.VISIBLE) { - inputFrame.getBoundsOnScreen(mTempRect); + inputFrame.getLocationInWindow(mTempPos); + mTempRect.set(mTempPos[0], mTempPos[1], + mTempPos[0] + inputFrame.getWidth(), + mTempPos[1] + inputFrame.getHeight()); touchableRegion = new Region(mTempRect); } break; case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: if (inputFrame.getVisibility() == View.VISIBLE) { - inputFrame.getBoundsOnScreen(mTempRect); - mTempRect.top = originalInsets.contentTopInsets; + inputFrame.getLocationInWindow(mTempPos); + mTempRect.set(mTempPos[0], originalInsets.contentTopInsets, + mTempPos[0] + inputFrame.getWidth() , + mTempPos[1] + inputFrame.getHeight()); touchableRegion = new Region(mTempRect); } break; case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: if (inputFrame.getVisibility() == View.VISIBLE) { - inputFrame.getBoundsOnScreen(mTempRect); - mTempRect.top = originalInsets.visibleTopInsets; + inputFrame.getLocationInWindow(mTempPos); + mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets, + mTempPos[0] + inputFrame.getWidth(), + mTempPos[1] + inputFrame.getHeight()); touchableRegion = new Region(mTempRect); } break; diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 815e4f0c9071..d71faee4cc8d 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1205,13 +1205,16 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } static Uri readFrom(Parcel parcel) { - return new HierarchicalUri( - parcel.readString8(), - Part.readFrom(parcel), - PathPart.readFrom(parcel), - Part.readFrom(parcel), - Part.readFrom(parcel) - ); + final String scheme = parcel.readString8(); + final Part authority = Part.readFrom(parcel); + // In RFC3986 the path should be determined based on whether there is a scheme or + // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). + final boolean hasSchemeOrAuthority = + (scheme != null && scheme.length() > 0) || !authority.isEmpty(); + final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); + final Part query = Part.readFrom(parcel); + final Part fragment = Part.readFrom(parcel); + return new HierarchicalUri(scheme, authority, path, query, fragment); } public int describeContents() { @@ -2270,6 +2273,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } + static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) { + final PathPart path = readFrom(parcel); + return hasSchemeOrAuthority ? makeAbsolute(path) : path; + } + /** * Creates a path from the encoded string. * diff --git a/core/java/android/permission/ILegacyPermissionManager.aidl b/core/java/android/permission/ILegacyPermissionManager.aidl index f1f083668711..78e12de04e89 100644 --- a/core/java/android/permission/ILegacyPermissionManager.aidl +++ b/core/java/android/permission/ILegacyPermissionManager.aidl @@ -49,4 +49,6 @@ interface ILegacyPermissionManager { void grantDefaultPermissionsToActiveLuiApp(in String packageName, int userId); void revokeDefaultPermissionsFromLuiApps(in String[] packageNames, int userId); + + void grantDefaultPermissionsToCarrierServiceApp(in String packageName, int userId); } diff --git a/core/java/android/permission/LegacyPermissionManager.java b/core/java/android/permission/LegacyPermissionManager.java index a4fa11b5121b..57776857864e 100644 --- a/core/java/android/permission/LegacyPermissionManager.java +++ b/core/java/android/permission/LegacyPermissionManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteException; @@ -244,4 +245,20 @@ public final class LegacyPermissionManager { e.rethrowFromSystemServer(); } } + + /** + * Grant permissions to a newly set Carrier Services app. + * @param packageName The newly set Carrier Services app + * @param userId The user for which to grant the permissions. + * @hide + */ + public void grantDefaultPermissionsToCarrierServiceApp(@NonNull String packageName, + @UserIdInt int userId) { + try { + mLegacyPermissionManager.grantDefaultPermissionsToCarrierServiceApp(packageName, + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index d25e456270ae..37f44e98c165 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -731,6 +731,13 @@ public final class DeviceConfig { public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service"; + /** + * Namespace for Vendor System Native related features. + * + * @hide + */ + public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 34647b144be1..731b5315c7fb 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7679,20 +7679,6 @@ public final class Settings { "zen_settings_suggestion_viewed"; /** - * State of whether review notification permissions notification needs to - * be shown the user, and whether the user has interacted. - * - * Valid values: - * -1 = UNKNOWN - * 0 = SHOULD_SHOW - * 1 = USER_INTERACTED - * 2 = DISMISSED - * @hide - */ - public static final String REVIEW_PERMISSIONS_NOTIFICATION_STATE = - "review_permissions_notification_state"; - - /** * Whether the in call notification is enabled to play sound during calls. The value is * boolean (1 or 0). * @hide @@ -16966,6 +16952,21 @@ public final class Settings { "managed_provisioning_defer_provisioning_to_role_holder"; /** + * State of whether review notification permissions notification needs to + * be shown the user, and whether the user has interacted. + * + * Valid values: + * -1 = UNKNOWN + * 0 = SHOULD_SHOW + * 1 = USER_INTERACTED + * 2 = DISMISSED + * 3 = RESHOWN + * @hide + */ + public static final String REVIEW_PERMISSIONS_NOTIFICATION_STATE = + "review_permissions_notification_state"; + + /** * Settings migrated from Wear OS settings provider. * @hide */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 6a9afdb84e18..47fc120c9d4f 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -999,6 +999,14 @@ public class DreamService extends Service implements Window.Callback { return mDreamServiceWrapper; } + @Override + public boolean onUnbind(Intent intent) { + // We must unbind from any overlay connection if we are unbound before finishing. + mOverlayConnection.unbind(this); + + return super.onUnbind(intent); + } + /** * Stops the dream and detaches from the window. * <p> diff --git a/core/java/android/service/games/GameSessionTrampolineActivity.java b/core/java/android/service/games/GameSessionTrampolineActivity.java index e890876df3e7..b23791842284 100644 --- a/core/java/android/service/games/GameSessionTrampolineActivity.java +++ b/core/java/android/service/games/GameSessionTrampolineActivity.java @@ -105,6 +105,7 @@ public final class GameSessionTrampolineActivity extends Activity { FUTURE_KEY); future.completeExceptionally(e); finish(); + overridePendingTransition(0, 0); } } @@ -125,5 +126,6 @@ public final class GameSessionTrampolineActivity extends Activity { FUTURE_KEY); future.complete(new GameSessionActivityResult(resultCode, data)); finish(); + overridePendingTransition(0, 0); } } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 77591a7efb5e..ebbe64c396e7 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -785,9 +785,7 @@ public final class Choreographer { } } frameTimeNanos = startNanos - lastFrameOffset; - DisplayEventReceiver.VsyncEventData latestVsyncEventData = - mDisplayEventReceiver.getLatestVsyncEventData(); - frameData.updateFrameData(frameTimeNanos, latestVsyncEventData); + frameData.updateFrameData(frameTimeNanos); } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -885,9 +883,7 @@ public final class Choreographer { } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; - DisplayEventReceiver.VsyncEventData latestVsyncEventData = - mDisplayEventReceiver.getLatestVsyncEventData(); - frameData.updateFrameData(frameTimeNanos, latestVsyncEventData); + frameData.updateFrameData(frameTimeNanos); } } } @@ -1022,6 +1018,11 @@ public final class Choreographer { return mVsyncId; } + /** Reset the vsync ID to invalid. */ + void resetVsyncId() { + mVsyncId = FrameInfo.INVALID_VSYNC_ID; + } + /** * The time in {@link System#nanoTime()} timebase which this frame is expected to be * presented. @@ -1069,12 +1070,14 @@ public final class Choreographer { private FrameTimeline[] mFrameTimelines; private FrameTimeline mPreferredFrameTimeline; - void updateFrameData(long frameTimeNanos, - DisplayEventReceiver.VsyncEventData latestVsyncEventData) { + void updateFrameData(long frameTimeNanos) { mFrameTimeNanos = frameTimeNanos; - mFrameTimelines = convertFrameTimelines(latestVsyncEventData); - mPreferredFrameTimeline = - mFrameTimelines[latestVsyncEventData.preferredFrameTimelineIndex]; + for (FrameTimeline ft : mFrameTimelines) { + // The ID is no longer valid because the frame time that was registered with the ID + // no longer matches. + // TODO(b/205721584): Ask SF for valid vsync information. + ft.resetVsyncId(); + } } /** The time in nanoseconds when the frame started being rendered. */ diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index e6cf68367ae6..583252756b92 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -47,6 +47,7 @@ public class InsetsSource implements Parcelable { private final Rect mFrame; private @Nullable Rect mVisibleFrame; private boolean mVisible; + private boolean mInsetsRoundedCornerFrame; private final Rect mTmpFrame = new Rect(); @@ -63,6 +64,7 @@ public class InsetsSource implements Parcelable { mVisibleFrame = other.mVisibleFrame != null ? new Rect(other.mVisibleFrame) : null; + mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame; } public void set(InsetsSource other) { @@ -71,6 +73,7 @@ public class InsetsSource implements Parcelable { mVisibleFrame = other.mVisibleFrame != null ? new Rect(other.mVisibleFrame) : null; + mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame; } public void setFrame(int left, int top, int right, int bottom) { @@ -110,6 +113,14 @@ public class InsetsSource implements Parcelable { return mVisibleFrame == null || !mVisibleFrame.isEmpty(); } + public boolean getInsetsRoundedCornerFrame() { + return mInsetsRoundedCornerFrame; + } + + public void setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) { + mInsetsRoundedCornerFrame = insetsRoundedCornerFrame; + } + /** * Calculates the insets this source will cause to a client window. * @@ -225,6 +236,7 @@ public class InsetsSource implements Parcelable { pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString()); } pw.print(" visible="); pw.print(mVisible); + pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame); pw.println(); } @@ -247,6 +259,7 @@ public class InsetsSource implements Parcelable { if (mVisible != that.mVisible) return false; if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; + if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false; return mFrame.equals(that.mFrame); } @@ -256,6 +269,7 @@ public class InsetsSource implements Parcelable { result = 31 * result + mFrame.hashCode(); result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0); result = 31 * result + (mVisible ? 1 : 0); + result = 31 * result + (mInsetsRoundedCornerFrame ? 1 : 0); return result; } @@ -268,6 +282,7 @@ public class InsetsSource implements Parcelable { mVisibleFrame = null; } mVisible = in.readBoolean(); + mInsetsRoundedCornerFrame = in.readBoolean(); } @Override @@ -286,6 +301,7 @@ public class InsetsSource implements Parcelable { dest.writeInt(0); } dest.writeBoolean(mVisible); + dest.writeBoolean(mInsetsRoundedCornerFrame); } @Override @@ -294,6 +310,7 @@ public class InsetsSource implements Parcelable { + "mType=" + InsetsState.typeToString(mType) + ", mFrame=" + mFrame.toShortString() + ", mVisible=" + mVisible + + ", mInsetsRoundedCornerFrame=" + mInsetsRoundedCornerFrame + "}"; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index eb746080de15..9d6b982c3571 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -294,9 +294,16 @@ public class InsetsState implements Parcelable { return RoundedCorners.NO_ROUNDED_CORNERS; } // If mRoundedCornerFrame is set, we should calculate the new RoundedCorners based on this - // frame. It's used for split-screen mode and devices with a task bar. - if (!mRoundedCornerFrame.isEmpty() && !mRoundedCornerFrame.equals(mDisplayFrame)) { - return mRoundedCorners.insetWithFrame(frame, mRoundedCornerFrame); + // frame. + final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame); + for (InsetsSource source : mSources) { + if (source != null && source.getInsetsRoundedCornerFrame()) { + final Insets insets = source.calculateInsets(roundedCornerFrame, false); + roundedCornerFrame.inset(insets); + } + } + if (!roundedCornerFrame.isEmpty() && !roundedCornerFrame.equals(mDisplayFrame)) { + return mRoundedCorners.insetWithFrame(frame, roundedCornerFrame); } if (mDisplayFrame.equals(frame)) { return mRoundedCorners; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cfe44bbbf3c6..5bc340b76f56 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -488,6 +488,13 @@ public interface WindowManager extends ViewManager { int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION = 0x8; /** + * Transition flag: Keyguard is going away to the launcher, and it needs us to clear the task + * snapshot of the launcher because it has changed something in the Launcher window. + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT = 0x16; + + /** * Transition flag: App is crashed. * @hide */ @@ -527,6 +534,7 @@ public interface WindowManager extends ViewManager { TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION, TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER, TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT, TRANSIT_FLAG_APP_CRASHED, TRANSIT_FLAG_OPEN_BEHIND, TRANSIT_FLAG_KEYGUARD_LOCKED, @@ -717,8 +725,8 @@ public interface WindowManager extends ViewManager { /** * Returns a set of {@link WindowMetrics} for the given display. Each WindowMetrics instance - * is the maximum WindowMetrics for a device state, including rotations. This is not guaranteed - * to include all possible device states. + * is the maximum WindowMetrics for a device state. This is not guaranteed to include all + * possible device states. * * This API can only be used by Launcher. * diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 94f633314b4e..4d07171d3086 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -48,6 +48,7 @@ public interface WindowManagerPolicyConstants { int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1; int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2; int KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS = 1 << 3; + int KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT = 1 << 4; // Flags used for indicating whether the internal and/or external input devices // of some type are available. diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java index f95838516927..599e6d24600c 100644 --- a/core/java/com/android/internal/app/AppLocaleStore.java +++ b/core/java/com/android/internal/app/AppLocaleStore.java @@ -51,13 +51,17 @@ class AppLocaleStore { appSupportedLocales.add(packageLocaleList.get(i)); } } else { - localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE; + localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE_IN_APP; } } else if (localeConfig.getStatus() == LocaleConfig.STATUS_NOT_SPECIFIED) { - localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; String[] languages = getAssetLocales(context, packageName); - for (String language : languages) { - appSupportedLocales.add(Locale.forLanguageTag(language)); + if (languages.length > 0) { + localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET; + for (String language : languages) { + appSupportedLocales.add(Locale.forLanguageTag(language)); + } + } else { + localeStatus = LocaleStatus.ASSET_LOCALE_IS_EMPTY; } } } @@ -89,7 +93,8 @@ class AppLocaleStore { static class AppLocaleResult { enum LocaleStatus { UNKNOWN_FAILURE, - NO_SUPPORTED_LANGUAGE, + NO_SUPPORTED_LANGUAGE_IN_APP, + ASSET_LOCALE_IS_EMPTY, GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG, GET_SUPPORTED_LANGUAGE_FROM_ASSET, } diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 314b0a0c81db..a06ba9be4689 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -247,6 +247,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O // In order to make the list view work with CollapsingToolbarLayout, // we have to enable the nested scrolling feature of the list view. getListView().setNestedScrollingEnabled(true); + getListView().setDivider(null); } @Override diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 18fde4794969..5fe111148c91 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -27,7 +27,6 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; -import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.R; @@ -222,6 +221,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { convertView = mInflater.inflate( R.layout.app_language_picker_current_locale_item, parent, false); title = convertView.findViewById(R.id.language_picker_item); + addStateDescriptionIntoCurrentLocaleItem(convertView); } else { convertView = mInflater.inflate( R.layout.language_picker_item, parent, false); @@ -234,6 +234,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { if (!(convertView instanceof ViewGroup)) { convertView = mInflater.inflate( R.layout.app_language_picker_current_locale_item, parent, false); + addStateDescriptionIntoCurrentLocaleItem(convertView); } updateTextView( convertView, convertView.findViewById(R.id.language_picker_item), position); @@ -369,4 +370,9 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { : View.TEXT_DIRECTION_LTR); } } + + private void addStateDescriptionIntoCurrentLocaleItem(View root) { + String description = root.getContext().getResources().getString(R.string.checked); + root.setStateDescription(description); + } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 6424989c6b4f..1b52aa93a51d 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -28,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; @@ -189,7 +190,8 @@ public class InteractionJankMonitor { public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; public static final int CUJ_SPLIT_SCREEN_ENTER = 49; public static final int CUJ_SPLIT_SCREEN_EXIT = 50; - public static final int CUJ_SPLIT_SCREEN_RESIZE = 51; + public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. + public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; private static final int NO_STATSD_LOGGING = -1; @@ -249,6 +251,7 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE, }; @@ -321,6 +324,7 @@ public class InteractionJankMonitor { CUJ_SUW_LOADING_SCREEN_FOR_STATUS, CUJ_SPLIT_SCREEN_ENTER, CUJ_SPLIT_SCREEN_EXIT, + CUJ_LOCKSCREEN_LAUNCH_CAMERA, CUJ_SPLIT_SCREEN_RESIZE }) @Retention(RetentionPolicy.SOURCE) @@ -742,6 +746,8 @@ public class InteractionJankMonitor { return "SPLIT_SCREEN_ENTER"; case CUJ_SPLIT_SCREEN_EXIT: return "SPLIT_SCREEN_EXIT"; + case CUJ_LOCKSCREEN_LAUNCH_CAMERA: + return "CUJ_LOCKSCREEN_LAUNCH_CAMERA"; case CUJ_SPLIT_SCREEN_RESIZE: return "CUJ_SPLIT_SCREEN_RESIZE"; } diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index 94a8ae5a8a67..f2c27a494fc9 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -76,6 +76,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa ContentResolver contentResolver = context.getContentResolver(); mPostScrollDelayMillis = Settings.Global.getLong(contentResolver, SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT); + Log.d(TAG, "screenshot.scroll_capture_delay = " + mPostScrollDelayMillis); } /** Based on ViewRootImpl#updateColorModeIfNeeded */ @@ -271,6 +272,13 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa Rect viewCaptureArea = new Rect(scrollResult.availableArea); viewCaptureArea.offset(0, -scrollResult.scrollDelta); + view.postOnAnimationDelayed( + () -> doCapture(scrollResult, view, viewCaptureArea, onComplete), + mPostScrollDelayMillis); + } + + private void doCapture(ScrollResult scrollResult, V view, Rect viewCaptureArea, + Consumer<Rect> onComplete) { int result = mRenderer.renderView(view, viewCaptureArea); if (result == HardwareRenderer.SYNC_OK || result == HardwareRenderer.SYNC_REDRAW_REQUESTED) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4075c5f4d8ae..217166c6810b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -377,6 +377,8 @@ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" /> <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" /> <protected-broadcast android:name="com.android.internal.action.EUICC_FACTORY_RESET" /> + <protected-broadcast + android:name="com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS" /> <protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" /> <protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" /> <protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" /> @@ -6790,7 +6792,7 @@ android:excludeFromRecents="true" android:exported="true" android:permission="android.permission.MANAGE_GAME_ACTIVITY" - android:theme="@style/Theme.Translucent.NoTitleBar"> + android:theme="@style/Theme.GameSessionTrampoline"> </activity> <receiver android:name="com.android.server.BootReceiver" diff --git a/core/res/res/layout/app_language_picker_current_locale_item.xml b/core/res/res/layout/app_language_picker_current_locale_item.xml index bf6d9639791a..990e26c8f6be 100644 --- a/core/res/res/layout/app_language_picker_current_locale_item.xml +++ b/core/res/res/layout/app_language_picker_current_locale_item.xml @@ -39,6 +39,6 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_check_24dp" - app:tint="#0F9D58"/> + app:tint="?attr/colorAccentPrimaryVariant"/> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/app_language_picker_system_current.xml b/core/res/res/layout/app_language_picker_system_current.xml index 341ee2528671..300da25ea445 100644 --- a/core/res/res/layout/app_language_picker_system_current.xml +++ b/core/res/res/layout/app_language_picker_system_current.xml @@ -40,6 +40,6 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_check_24dp" - app:tint="#0F9D58"/> + app:tint="?attr/colorAccentPrimaryVariant"/> </LinearLayout> </LinearLayout> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 19b72bfbe6c0..edaf8cf279e3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2415,8 +2415,12 @@ <!-- Is the system user the only user allowed to dream. --> <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool> + <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to + assistant activities (ACTIVITY_TYPE_ASSISTANT) --> + <bool name="config_dismissDreamOnActivityStart">true</bool> + <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. --> - <string name ="config_loggable_dream_prefix" translatable="false"></string> + <string name="config_loggable_dream_prefix" translatable="false"></string> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 012030e9b393..8226ec435533 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2221,6 +2221,7 @@ <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" /> <java-symbol type="array" name="config_disabledDreamComponents" /> + <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="string" name="config_loggable_dream_prefix" /> <java-symbol type="string" name="config_dozeComponent" /> <java-symbol type="string" name="enable_explore_by_touch_warning_title" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index bf42da080390..a60862b74e15 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -894,6 +894,22 @@ please see themes_device_defaults.xml. <!-- @hide Special theme for the default system Activity-based Alert dialogs. --> <style name="Theme.Dialog.Confirmation" parent="Theme.DeviceDefault.Dialog.Alert.DayNight" /> + <!-- @hide Theme for GameSessionTrampolineActivity that prevents showing UI and activity + transitions. --> + <style name="Theme.GameSessionTrampoline"> + <item name="backgroundDimEnabled">false</item> + <item name="colorBackgroundCacheHint">@null</item> + <item name="navigationBarColor">@color/transparent</item> + <item name="statusBarColor">@color/transparent</item> + <item name="windowAnimationStyle">@null</item> + <item name="windowBackground">@null</item> + <item name="windowContentOverlay">@null</item> + <item name="windowDrawsSystemBarBackgrounds">true</item> + <item name="windowIsFloating">true</item> + <item name="windowIsTranslucent">true</item> + <item name="windowNoTitle">true</item> + </style> + <!-- Theme for a window that looks like a toast. --> <style name="Theme.Toast" parent="Theme.DeviceDefault.Dialog"> <item name="windowBackground">?attr/toastFrameBackground</item> diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index e083b0d460a2..3733bfa586d1 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -48,6 +48,7 @@ public class UriTest extends TestCase { public void testParcelling() { parcelAndUnparcel(Uri.parse("foo:bob%20lee")); parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(Uri.fromParts("https", "www.google.com", null)); parcelAndUnparcel(new Uri.Builder() .scheme("http") .authority("crazybob.org") @@ -890,9 +891,62 @@ public class UriTest extends TestCase { Throwable targetException = expected.getTargetException(); // Check that the exception was thrown for the correct reason. assertEquals("Unknown representation: 0", targetException.getMessage()); + } finally { + parcel.recycle(); } } + private Uri buildUriFromRawParcel(boolean argumentsEncoded, + String scheme, + String authority, + String path, + String query, + String fragment) { + // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). + final int representation = argumentsEncoded ? 1 : 2; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeInt(3); // hierarchical + parcel.writeString8(scheme); + parcel.writeInt(representation); + parcel.writeString8(authority); + parcel.writeInt(representation); + parcel.writeString8(path); + parcel.writeInt(representation); + parcel.writeString8(query); + parcel.writeInt(representation); + parcel.writeString8(fragment); + parcel.setDataPosition(0); + return Uri.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + + public void testUnparcelMalformedPath() { + // Regression tests for b/171966843. + + // Test cases with arguments encoded (covering testing `scheme` * `authority` options). + Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/@evil.com", uri0.toString()); + Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); + Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + assertEquals("http::/@evil.com", uri2.toString()); + Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + assertEquals("@evil.com", uri3.toString()); + + // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). + Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/%40evil.com", uriA.toString()); + Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + assertEquals("//google.com/%40evil.com", uriB.toString()); + Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + assertEquals("http::/%40evil.com", uriC.toString()); + Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); diff --git a/libs/WindowManager/Shell/res/layout/split_decor.xml b/libs/WindowManager/Shell/res/layout/split_decor.xml index dfb90affe7f6..443ecb2ed3f3 100644 --- a/libs/WindowManager/Shell/res/layout/split_decor.xml +++ b/libs/WindowManager/Shell/res/layout/split_decor.xml @@ -20,8 +20,8 @@ android:layout_width="match_parent"> <ImageView android:id="@+id/split_resizing_icon" - android:layout_height="@*android:dimen/starting_surface_icon_size" - android:layout_width="@*android:dimen/starting_surface_icon_size" + android:layout_height="@dimen/split_icon_size" + android:layout_width="@dimen/split_icon_size" android:layout_gravity="center" android:scaleType="fitCenter" android:padding="0dp" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index c21381d1486a..1dac9caba01e 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -87,6 +87,8 @@ <!-- How high we lift the divider when touching --> <dimen name="docked_stack_divider_lift_elevation">4dp</dimen> + <!-- Icon size for split screen --> + <dimen name="split_icon_size">72dp</dimen> <!-- Divider handle size for legacy split screen --> <dimen name="docked_divider_handle_width">16dp</dimen> <dimen name="docked_divider_handle_height">2dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 5dc6bd19853a..de30dbbe7e46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -73,6 +73,8 @@ public class SplitDecorManager extends WindowlessWindowManager { private Rect mBounds = new Rect(); private ValueAnimator mFadeAnimator; + private int mIconSize; + public SplitDecorManager(Configuration configuration, IconProvider iconProvider, SurfaceSession surfaceSession) { super(configuration, null /* rootSurface */, null /* hostInputToken */); @@ -104,6 +106,7 @@ public class SplitDecorManager extends WindowlessWindowManager { mHostLeash = rootLeash; mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this); + mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size); final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context) .inflate(R.layout.split_decor, null); mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon); @@ -171,14 +174,14 @@ public class SplitDecorManager extends WindowlessWindowManager { WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); - lp.width = mIcon.getIntrinsicWidth(); - lp.height = mIcon.getIntrinsicHeight(); + lp.width = mIconSize; + lp.height = mIconSize; mViewHost.relayout(lp); t.setLayer(mIconLeash, Integer.MAX_VALUE); } t.setPosition(mIconLeash, - newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2, - newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2); + newBounds.width() / 2 - mIconSize / 2, + newBounds.height() / 2 - mIconSize / 2); boolean show = newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height(); if (show != mShown) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index 0cea36ed48c8..28f59b53b5b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -111,8 +111,7 @@ public class DropZoneView extends FrameLayout { mColorDrawable = new ColorDrawable(); setBackgroundDrawable(mColorDrawable); - final int iconSize = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.starting_surface_icon_size); + final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size); mSplashScreenView = new ImageView(context); mSplashScreenView.setScaleType(ImageView.ScaleType.FIT_CENTER); addView(mSplashScreenView, 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 9d6e34ddd9d7..91f9d2522397 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 @@ -1786,15 +1786,18 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow() { if (mMainStage.isActive()) { + final boolean isMainStage = mMainStageListener == this; if (!ENABLE_SHELL_TRANSITIONS) { - StageCoordinator.this.exitSplitScreen(null /* childrenToTop */, + StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + return; } + final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + prepareExitSplitScreen(stageType, wct); mSplitTransitions.startDismissTransition(null /* transition */, wct, - StageCoordinator.this, STAGE_TYPE_UNDEFINED, + StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index b7c80df03ce2..c9cab39b7d8b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -16,8 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -31,6 +29,7 @@ import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSuppo import org.junit.After import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -76,23 +75,23 @@ class AppPairsTestCannotPairNonResizeableApps( resetMultiWindowConfig(instrumentation) } - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @Presubmit + @Ignore @Test fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() - @Presubmit + @Ignore @Test fun onlyResizeableAppWindowVisible() { val nonResizeableApp = nonResizeableApp diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index 1ac664eb1f62..60c32c99d1ff 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -16,8 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -29,6 +27,7 @@ import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -57,23 +56,23 @@ class AppPairsTestPairPrimaryAndSecondaryApps( } } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -82,7 +81,7 @@ class AppPairsTestPairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test fun appsEndingBounds() { testSpec.assertLayersEnd { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt index 57bcbc093a62..24869a802167 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -16,9 +16,7 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit import android.view.Display -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -33,6 +31,7 @@ import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSuppo import org.junit.After import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -89,23 +88,23 @@ class AppPairsTestSupportPairNonResizeableApps( resetMultiWindowConfig(instrumentation) } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test fun bothAppWindowVisible() { val nonResizeableApp = nonResizeableApp diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 12910dd74271..007415d19860 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -17,8 +17,6 @@ package com.android.wm.shell.flicker.apppairs import android.os.SystemClock -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -30,6 +28,7 @@ import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -65,19 +64,19 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() - @Presubmit + @Ignore @Test fun bothAppWindowsInvisible() { testSpec.assertWmEnd { @@ -86,7 +85,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test fun appsStartingBounds() { testSpec.assertLayersStart { @@ -98,7 +97,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @FlakyTest + @Ignore @Test fun appsEndingBounds() { testSpec.assertLayersEnd { @@ -107,7 +106,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 863c3aff63a2..3e17948b4a84 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -18,9 +18,7 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation import android.content.Context -import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -42,6 +40,7 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.testapp.Components import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) { @@ -145,35 +144,35 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) append("$primaryApp $secondaryApp") } - @FlakyTest(bugId = 186510496) + @Ignore @Test open fun navBarLayerIsVisible() { testSpec.navBarLayerIsVisible() } - @Presubmit + @Ignore @Test open fun statusBarLayerIsVisible() { testSpec.statusBarLayerIsVisible() } - @Presubmit + @Ignore @Test open fun navBarWindowIsVisible() { testSpec.navBarWindowIsVisible() } - @Presubmit + @Ignore @Test open fun statusBarWindowIsVisible() { testSpec.statusBarWindowIsVisible() } - @Presubmit + @Ignore @Test open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @Presubmit + @Ignore @Test open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index f2f4877a44c4..b0c3ba20d948 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -16,9 +16,7 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -32,6 +30,7 @@ import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -60,15 +59,15 @@ class RotateTwoLaunchedAppsInAppPairsMode( } } - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @Presubmit + @Ignore @Test override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - @Presubmit + @Ignore @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -77,23 +76,23 @@ class RotateTwoLaunchedAppsInAppPairsMode( } } - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test fun appPairsPrimaryBoundsIsVisibleAtEnd() = testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, primaryApp.component) - @Presubmit + @Ignore @Test fun appPairsSecondaryBoundsIsVisibleAtEnd() = testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, secondaryApp.component) - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index 2a173d16004f..ae56c7732a4d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -16,9 +16,7 @@ package com.android.wm.shell.flicker.apppairs -import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -32,6 +30,7 @@ import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -60,31 +59,31 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( } } - @Presubmit + @Ignore @Test fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - @Presubmit + @Ignore @Test override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() - @Presubmit + @Ignore @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @Presubmit + @Ignore @Test override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() - @Presubmit + @Ignore @Test override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - @FlakyTest(bugId = 206753786) + @Ignore @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - @Presubmit + @Ignore @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -93,13 +92,13 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( } } - @Presubmit + @Ignore @Test fun appPairsPrimaryBoundsIsVisibleAtEnd() = testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, primaryApp.component) - @Presubmit + @Ignore @Test fun appPairsSecondaryBoundsIsVisibleAtEnd() = testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt index 670fbd810907..b1f1c9e539df 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.apppairs import android.view.Surface -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.setRotation @@ -26,6 +25,7 @@ import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTrans import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.Assume.assumeFalse import org.junit.Before +import org.junit.Ignore import org.junit.Test abstract class RotateTwoLaunchedAppsTransition( @@ -62,13 +62,13 @@ abstract class RotateTwoLaunchedAppsTransition( super.setup() } - @FlakyTest + @Ignore @Test override fun navBarLayerIsVisible() { super.navBarLayerIsVisible() } - @FlakyTest + @Ignore @Test override fun navBarLayerRotatesAndScales() { super.navBarLayerRotatesAndScales() diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml index 7c3f5a566bdb..a596a9a59ee8 100644 --- a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml +++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml @@ -28,7 +28,8 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> <View android:id="@+id/divider1" @@ -43,7 +44,8 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> <View android:id="@+id/divider2" @@ -58,7 +60,8 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> <View android:id="@+id/divider3" @@ -73,5 +76,6 @@ style="@style/SettingsLibActionButton" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_weight="1" + android:hyphenationFrequency="normalFast"/> </LinearLayout> diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java index 7c9a045f4a42..fc0e05f7fb46 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java @@ -122,9 +122,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw * Adds a listener for switch changes */ public void addOnSwitchChangeListener(OnMainSwitchChangeListener listener) { - if (mMainSwitchBar == null) { + if (!mSwitchChangeListeners.contains(listener)) { mSwitchChangeListeners.add(listener); - } else { + } + if (mMainSwitchBar != null) { mMainSwitchBar.addOnSwitchChangeListener(listener); } } @@ -133,9 +134,8 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw * Remove a listener for switch changes */ public void removeOnSwitchChangeListener(OnMainSwitchChangeListener listener) { - if (mMainSwitchBar == null) { - mSwitchChangeListeners.remove(listener); - } else { + mSwitchChangeListeners.remove(listener); + if (mMainSwitchBar != null) { mMainSwitchBar.removeOnSwitchChangeListener(listener); } } 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 b038d59e9fe4..5693c2f22d1e 100644 --- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java +++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java @@ -42,15 +42,13 @@ public class WorkPolicyUtils { private static final int USER_NULL = -10000; public WorkPolicyUtils( - Context applicationContext, - PackageManager mPm, - UserManager mUm, - DevicePolicyManager mDpm + Context context ) { - mContext = applicationContext; - mPackageManager = mPm; - mUserManager = mUm; - mDevicePolicyManager = mDpm; + mContext = context; + mPackageManager = context.getPackageManager(); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mDevicePolicyManager = + (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java index 552fa11a42b7..3514932d4e8d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java @@ -48,6 +48,12 @@ public class MediaOutputConstants { "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG"; /** + * An intent action to launch media output broadcast dialog. + */ + public static final String ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG = + "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG"; + + /** * Settings package name. */ public static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index f1b23d5733af..4b7d0d2a3c74 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -597,6 +597,7 @@ public class SettingsBackupTest { Settings.Global.CLOCKWORK_HOME_READY, Settings.Global.WATCHDOG_TIMEOUT_MILLIS, Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER, + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, Settings.Global.Wearable.BATTERY_SAVER_MODE, Settings.Global.Wearable.COMBINED_LOCATION_ENABLED, Settings.Global.Wearable.HAS_PAY_TOKENS, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6887d037c6f4..290ce345694e 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -931,6 +931,7 @@ android:exported="true"> <intent-filter> <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" /> + <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" /> <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" /> </intent-filter> </receiver> diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index ca557796462f..093589f8c636 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -20,6 +20,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.animation.PropertyValuesHolder +import android.animation.ValueAnimator import android.util.IntProperty import android.view.View import android.view.ViewGroup @@ -37,6 +38,7 @@ class ViewHierarchyAnimator { private const val DEFAULT_DURATION = 500L private val DEFAULT_INTERPOLATOR = Interpolators.STANDARD private val DEFAULT_ADDITION_INTERPOLATOR = Interpolators.STANDARD_DECELERATE + private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE /** The properties used to animate the view bounds. */ private val PROPERTIES = mapOf( @@ -113,7 +115,7 @@ class ViewHierarchyAnimator { } val listener = createUpdateListener(interpolator, duration, ephemeral) - recursivelyAddListener(rootView, listener) + addListener(rootView, listener, recursive = true) return true } @@ -183,7 +185,7 @@ class ViewHierarchyAnimator { val listener = createAdditionListener( origin, interpolator, duration, ignorePreviousValues = !includeMargins ) - recursivelyAddListener(rootView, listener) + addListener(rootView, listener, recursive = true) return true } @@ -298,6 +300,183 @@ class ViewHierarchyAnimator { } /** + * Animates the removal of [rootView] and its children from the hierarchy. It uses the given + * [interpolator] and [duration]. + * + * The end state of the animation is controlled by [destination]. This value can be any of + * the four corners, any of the four edges, or the center of the view. + */ + @JvmOverloads + fun animateRemoval( + rootView: View, + destination: Hotspot = Hotspot.CENTER, + interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, + duration: Long = DEFAULT_DURATION + ): Boolean { + if (!isVisible( + rootView.visibility, + rootView.left, + rootView.top, + rootView.right, + rootView.bottom + ) + ) { + return false + } + + val parent = rootView.parent as ViewGroup + + // Ensure that rootView's siblings animate nicely around the removal. + val listener = createUpdateListener( + interpolator, + duration, + ephemeral = true + ) + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + if (child == rootView) continue + addListener(child, listener, recursive = false) + } + + // Remove the view so that a layout update is triggered for the siblings and they + // animate to their next position while the view's removal is also animating. + parent.removeView(rootView) + // By adding the view to the overlay, we can animate it while it isn't part of the view + // hierarchy. It is correctly positioned because we have its previous bounds, and we set + // them manually during the animation. + parent.overlay.add(rootView) + + val startValues = mapOf( + Bound.LEFT to rootView.left, + Bound.TOP to rootView.top, + Bound.RIGHT to rootView.right, + Bound.BOTTOM to rootView.bottom + ) + val endValues = processEndValuesForRemoval( + destination, + rootView.left, + rootView.top, + rootView.right, + rootView.bottom + ) + + val boundsToAnimate = mutableSetOf<Bound>() + if (rootView.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) + if (rootView.top != endValues.getValue(Bound.TOP)) boundsToAnimate.add(Bound.TOP) + if (rootView.right != endValues.getValue(Bound.RIGHT)) boundsToAnimate.add(Bound.RIGHT) + if (rootView.bottom != endValues.getValue(Bound.BOTTOM)) { + boundsToAnimate.add(Bound.BOTTOM) + } + + startAnimation( + rootView, + boundsToAnimate, + startValues, + endValues, + interpolator, + duration, + ephemeral = true + ) + + if (rootView is ViewGroup) { + // Shift the children so they maintain a consistent position within the shrinking + // view. + shiftChildrenForRemoval(rootView, destination, endValues, interpolator, duration) + + // Fade out the children during the first half of the removal, so they don't clutter + // too much once the view becomes very small. Then we fade out the view itself, in + // case it has its own content and/or background. + val startAlphas = FloatArray(rootView.childCount) + for (i in 0 until rootView.childCount) { + startAlphas[i] = rootView.getChildAt(i).alpha + } + + val animator = ValueAnimator.ofFloat(1f, 0f) + animator.interpolator = Interpolators.ALPHA_OUT + animator.duration = duration / 2 + animator.addUpdateListener { animation -> + for (i in 0 until rootView.childCount) { + rootView.getChildAt(i).alpha = + (animation.animatedValue as Float) * startAlphas[i] + } + } + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + rootView.animate() + .alpha(0f) + .setInterpolator(Interpolators.ALPHA_OUT) + .setDuration(duration / 2) + .withEndAction { parent.overlay.remove(rootView) } + .start() + } + }) + animator.start() + } else { + // Fade out the view during the second half of the removal. + rootView.animate() + .alpha(0f) + .setInterpolator(Interpolators.ALPHA_OUT) + .setDuration(duration / 2) + .setStartDelay(duration / 2) + .withEndAction { parent.overlay.remove(rootView) } + .start() + } + + return true + } + + /** + * Animates the children of [rootView] so that its layout remains internally consistent as + * it shrinks towards [destination] and changes its bounds to [endValues]. + * + * Uses [interpolator] and [duration], which should match those of the removal animation. + */ + private fun shiftChildrenForRemoval( + rootView: ViewGroup, + destination: Hotspot, + endValues: Map<Bound, Int>, + interpolator: Interpolator, + duration: Long + ) { + for (i in 0 until rootView.childCount) { + val child = rootView.getChildAt(i) + val childStartValues = mapOf( + Bound.LEFT to child.left, + Bound.TOP to child.top, + Bound.RIGHT to child.right, + Bound.BOTTOM to child.bottom + ) + val childEndValues = processChildEndValuesForRemoval( + destination, + child.left, + child.top, + child.right, + child.bottom, + endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), + endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) + ) + + val boundsToAnimate = mutableSetOf<Bound>() + if (child.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) + if (child.top != endValues.getValue(Bound.TOP)) boundsToAnimate.add(Bound.TOP) + if (child.right != endValues.getValue(Bound.RIGHT)) boundsToAnimate.add(Bound.RIGHT) + if (child.bottom != endValues.getValue(Bound.BOTTOM)) { + boundsToAnimate.add(Bound.BOTTOM) + } + + startAnimation( + child, + boundsToAnimate, + childStartValues, + childEndValues, + interpolator, + duration, + ephemeral = true + ) + } + } + + /** * Returns whether the given [visibility] and bounds are consistent with a view being * currently visible on screen. */ @@ -312,7 +491,7 @@ class ViewHierarchyAnimator { } /** - * Compute the actual starting values based on the requested [origin] and on + * Computes the actual starting values based on the requested [origin] and on * [ignorePreviousValues]. * * If [origin] is null, the resolved start values will be the same as those passed in, or @@ -422,7 +601,140 @@ class ViewHierarchyAnimator { ) } - private fun recursivelyAddListener(view: View, listener: View.OnLayoutChangeListener) { + /** + * Computes a removal animation's end values based on the requested [destination] and the + * view's starting bounds. + * + * Examples: + * 1) destination=TOP + * x---------x x---------x x---------x x---------x x---------x + * | | | | | | x---------x + * | | -> | | -> x---------x -> -> + * | | x---------x + * x---------x + * 2) destination=BOTTOM_LEFT + * x---------x + * | | x-------x + * | | -> | | -> x----x -> -> + * | | | | | | x--x + * x---------x x-------x x----x x--x x + * 3) destination=CENTER + * x---------x + * | | x-------x x-----x + * | | -> | | -> | | -> x---x -> x + * | | x-------x x-----x + * x---------x + */ + private fun processEndValuesForRemoval( + destination: Hotspot, + left: Int, + top: Int, + right: Int, + bottom: Int + ): Map<Bound, Int> { + val endLeft = when (destination) { + Hotspot.CENTER -> (left + right) / 2 + Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP -> + left + Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> right + } + val endTop = when (destination) { + Hotspot.CENTER -> (top + bottom) / 2 + Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT -> + top + Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> bottom + } + val endRight = when (destination) { + Hotspot.CENTER -> (left + right) / 2 + Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM -> + right + Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> left + } + val endBottom = when (destination) { + Hotspot.CENTER -> (top + bottom) / 2 + Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT, Hotspot.LEFT -> + bottom + Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> top + } + + return mapOf( + Bound.LEFT to endLeft, + Bound.TOP to endTop, + Bound.RIGHT to endRight, + Bound.BOTTOM to endBottom + ) + } + + /** + * Computes the end values for the child of a view being removed, based on the child's + * starting bounds, the removal's [destination], and the [parentWidth] and [parentHeight]. + * + * The end values always represent the child's position after it has been translated so that + * its center is at the [destination]. + * + * Examples: + * 1) destination=TOP + * The child maintains its left and right positions, but is shifted up so that its + * center is on the parent's end top edge. + * 2) destination=BOTTOM_LEFT + * The child shifts so that its center is on the parent's end bottom left corner. + * 3) destination=CENTER + * The child shifts so that its own center is on the parent's end center. + */ + private fun processChildEndValuesForRemoval( + destination: Hotspot, + left: Int, + top: Int, + right: Int, + bottom: Int, + parentWidth: Int, + parentHeight: Int + ): Map<Bound, Int> { + val halfWidth = (right - left) / 2 + val halfHeight = (bottom - top) / 2 + + val endLeft = when (destination) { + Hotspot.CENTER -> (parentWidth / 2) - halfWidth + Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> -halfWidth + Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth + Hotspot.TOP, Hotspot.BOTTOM -> left + } + val endTop = when (destination) { + Hotspot.CENTER -> (parentHeight / 2) - halfHeight + Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> -halfHeight + Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> + parentHeight - halfHeight + Hotspot.LEFT, Hotspot.RIGHT -> top + } + val endRight = when (destination) { + Hotspot.CENTER -> (parentWidth / 2) + halfWidth + Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth + Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> halfWidth + Hotspot.TOP, Hotspot.BOTTOM -> right + } + val endBottom = when (destination) { + Hotspot.CENTER -> (parentHeight / 2) + halfHeight + Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> + parentHeight + halfHeight + Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> halfHeight + Hotspot.LEFT, Hotspot.RIGHT -> bottom + } + + return mapOf( + Bound.LEFT to endLeft, + Bound.TOP to endTop, + Bound.RIGHT to endRight, + Bound.BOTTOM to endBottom + ) + } + + private fun addListener( + view: View, + listener: View.OnLayoutChangeListener, + recursive: Boolean = false + ) { // Make sure that only one listener is active at a time. val previousListener = view.getTag(R.id.tag_layout_listener) if (previousListener != null && previousListener is View.OnLayoutChangeListener) { @@ -431,9 +743,9 @@ class ViewHierarchyAnimator { view.addOnLayoutChangeListener(listener) view.setTag(R.id.tag_layout_listener, listener) - if (view is ViewGroup) { + if (view is ViewGroup && recursive) { for (i in 0 until view.childCount) { - recursivelyAddListener(view.getChildAt(i), listener) + addListener(view.getChildAt(i), listener, recursive = true) } } } @@ -490,6 +802,8 @@ class ViewHierarchyAnimator { } }.toTypedArray() + (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() + val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders) animator.interpolator = interpolator animator.duration = duration diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 46dad02ddb45..dd45b6f39bdd 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -23,6 +23,7 @@ import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.cam.Cam import com.android.internal.graphics.cam.CamUtils.lstarFromInt import kotlin.math.absoluteValue +import kotlin.math.max import kotlin.math.roundToInt const val TAG = "ColorScheme" @@ -78,36 +79,32 @@ internal class HueSubtract(val amountDegrees: Double) : Hue { } internal class HueVibrantSecondary() : Hue { - val hueToRotations = listOf(Pair(24, 15), Pair(53, 15), Pair(91, 15), Pair(123, 15), - Pair(141, 15), Pair(172, 15), Pair(198, 15), Pair(234, 18), Pair(272, 18), - Pair(302, 18), Pair(329, 30), Pair(354, 15)) + val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12), + Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } } internal class HueVibrantTertiary() : Hue { - val hueToRotations = listOf(Pair(24, 30), Pair(53, 30), Pair(91, 15), Pair(123, 30), - Pair(141, 27), Pair(172, 27), Pair(198, 30), Pair(234, 35), Pair(272, 30), - Pair(302, 30), Pair(329, 60), Pair(354, 30)) + val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25), + Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } } internal class HueExpressiveSecondary() : Hue { - val hueToRotations = listOf(Pair(24, 95), Pair(53, 45), Pair(91, 45), Pair(123, 20), - Pair(141, 45), Pair(172, 45), Pair(198, 15), Pair(234, 15), - Pair(272, 45), Pair(302, 45), Pair(329, 45), Pair(354, 45)) + val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20), + Pair(141, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } } internal class HueExpressiveTertiary() : Hue { - val hueToRotations = listOf(Pair(24, 20), Pair(53, 20), Pair(91, 20), Pair(123, 45), - Pair(141, 20), Pair(172, 20), Pair(198, 90), Pair(234, 90), Pair(272, 20), - Pair(302, 20), Pair(329, 120), Pair(354, 120)) + val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45), + Pair(141, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120)) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) } @@ -140,18 +137,15 @@ internal interface Chroma { } } -internal class ChromaConstant(val chroma: Double) : Chroma { +internal class ChromaMinimum(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { - return chroma + return max(sourceColor.chroma.toDouble(), chroma) } } -internal class ChromaExpressiveNeutral() : Chroma { - val hueToChromas = listOf(Pair(24, 8), Pair(53, 8), Pair(91, 8), Pair(123, 8), - Pair(141, 6), Pair(172, 6), Pair(198, 8), Pair(234, 8), Pair(272, 8), - Pair(302, 8), Pair(329, 8), Pair(354, 8)) +internal class ChromaConstant(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { - return getSpecifiedChroma(sourceColor.hue, hueToChromas) + return chroma } } @@ -187,17 +181,17 @@ enum class Style(internal val coreSpec: CoreSpec) { n2 = TonalSpec(HueSource(), ChromaConstant(8.0)) )), VIBRANT(CoreSpec( - a1 = TonalSpec(HueSource(), ChromaConstant(48.0)), + a1 = TonalSpec(HueSource(), ChromaMinimum(48.0)), a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)), a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)), - n1 = TonalSpec(HueSource(), ChromaConstant(6.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(10.0)), n2 = TonalSpec(HueSource(), ChromaConstant(12.0)) )), EXPRESSIVE(CoreSpec( a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)), a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)), - a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(40.0)), - n1 = TonalSpec(HueAdd(15.0), ChromaExpressiveNeutral()), + a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)), + n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)), n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0)) )), RAINBOW(CoreSpec( @@ -231,8 +225,13 @@ class ColorScheme( constructor(@ColorInt seed: Int, darkTheme: Boolean): this(seed, darkTheme, Style.TONAL_SPOT) - constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean): - this(getSeedColor(wallpaperColors), darkTheme) + @JvmOverloads + constructor( + wallpaperColors: WallpaperColors, + darkTheme: Boolean, + style: Style = Style.TONAL_SPOT + ): + this(getSeedColor(wallpaperColors), darkTheme, style) val allAccentColors: List<Int> get() { diff --git a/packages/SystemUI/res/drawable/keyguard_framed_avatar_background.xml b/packages/SystemUI/res/drawable/keyguard_framed_avatar_background.xml new file mode 100644 index 000000000000..a461bf836d61 --- /dev/null +++ b/packages/SystemUI/res/drawable/keyguard_framed_avatar_background.xml @@ -0,0 +1,22 @@ +<?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 + --> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/kg_framed_avatar_size"/> + <solid android:color="@color/kg_user_avatar_frame"/> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml index 9cf09ff328c4..6f3362308484 100644 --- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml +++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml @@ -22,18 +22,25 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="end"> - <com.android.systemui.statusbar.phone.UserAvatarView - android:id="@+id/kg_multi_user_avatar" - android:layout_width="@dimen/kg_framed_avatar_size" - android:layout_height="@dimen/kg_framed_avatar_size" - android:layout_centerHorizontal="true" + <!-- We add a background behind the UserAvatarView with the same color and with a circular shape + so that this view can be expanded into a Dialog or an Activity. --> + <FrameLayout + android:id="@+id/kg_multi_user_avatar_with_background" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="top|end" android:layout_marginEnd="16dp" - systemui:avatarPadding="0dp" - systemui:badgeDiameter="18dp" - systemui:badgeMargin="1dp" - systemui:frameColor="@color/kg_user_avatar_frame" - systemui:framePadding="0dp" - systemui:frameWidth="0dp"> - </com.android.systemui.statusbar.phone.UserAvatarView> + android:background="@drawable/keyguard_framed_avatar_background"> + <com.android.systemui.statusbar.phone.UserAvatarView + android:id="@+id/kg_multi_user_avatar" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" + systemui:avatarPadding="0dp" + systemui:badgeDiameter="18dp" + systemui:badgeMargin="1dp" + systemui:frameColor="@color/kg_user_avatar_frame" + systemui:framePadding="0dp" + systemui:frameWidth="0dp"> + </com.android.systemui.statusbar.phone.UserAvatarView> + </FrameLayout> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 21e56976a074..008299bd9b1c 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -60,6 +60,8 @@ <dimen name="global_actions_grid_item_layout_height">80dp</dimen> + <dimen name="qs_brightness_margin_bottom">16dp</dimen> + <!-- For large screens the security footer appears below the footer, same as phones in portrait --> <dimen name="qs_security_footer_single_line_height">48dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f92d6238e863..e1fa5752e2d3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -213,6 +213,8 @@ <!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] --> <string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or your organization</string> + <!-- Notification text displayed when screenshots are blocked by an IT admin. [CHAR LIMIT=100] --> + <string name="screenshot_blocked_by_admin">Taking screenshots is blocked by your IT admin</string> <!-- Label for UI element which allows editing the screenshot [CHAR LIMIT=30] --> <string name="screenshot_edit_label">Edit</string> <!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] --> @@ -1994,7 +1996,8 @@ app for debugging. Will not be seen by users. [CHAR LIMIT=20] --> <string name="heap_dump_tile_name">Dump SysUI Heap</string> - <!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]--> + <!-- Title for the privacy indicators dialog, only appears as part of a11y descriptions [CHAR LIMIT=NONE] --> + <string name="ongoing_privacy_dialog_a11y_title">In use</string> <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]--> <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl index b2295b94127b..5a30901f1a8b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl @@ -16,17 +16,20 @@ package com.android.systemui.shared.system.smartspace; +import android.graphics.Rect; import com.android.systemui.shared.system.smartspace.SmartspaceState; // Methods for System UI to interface with Launcher to perform the unlock animation. interface ILauncherUnlockAnimationController { // Prepares Launcher for the unlock animation by setting scale/alpha/etc. to their starting // values. - void prepareForUnlock(boolean willAnimateSmartspace, int selectedPage); + void prepareForUnlock(boolean animateSmartspace, in Rect lockscreenSmartspaceBounds, + int selectedPage); // Set the unlock percentage. This is used when System UI is controlling each frame of the - // unlock animation, such as during a swipe to unlock touch gesture. - oneway void setUnlockAmount(float amount); + // unlock animation, such as during a swipe to unlock touch gesture. Will not apply this change + // if the unlock amount is animating unless forceIfAnimating is true. + oneway void setUnlockAmount(float amount, boolean forceIfAnimating); // Play a full unlock animation from 0f to 1f. This is used when System UI is unlocking from a // single action, such as biometric auth, and doesn't need to control individual frames. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 04c9a45af065..ea14b64b8b18 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -58,9 +58,7 @@ import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; -import java.util.HashSet; import java.util.Locale; -import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; @@ -134,14 +132,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardUnlockAnimationListener = new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { @Override - public void onSmartspaceSharedElementTransitionStarted() { - // The smartspace needs to be able to translate out of bounds in order to - // end up where the launcher's smartspace is, while its container is being - // swiped off the top of the screen. - setClipChildrenForUnlock(false); - } - - @Override public void onUnlockAnimationFinished() { // For performance reasons, reset this once the unlock animation ends. setClipChildrenForUnlock(true); @@ -390,41 +380,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mStatusArea != null) { PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X, x, props, animate); - - // If we're unlocking with the SmartSpace shared element transition, let the controller - // know that it should re-position our SmartSpace. - if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { - mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition(); - } - } - } - - /** Sets an alpha value on every child view except for the smartspace. */ - public void setChildrenAlphaExcludingSmartspace(float alpha) { - final Set<View> excludedViews = new HashSet<>(); - - if (mSmartspaceView != null) { - excludedViews.add(mStatusArea); - } - - // Don't change the alpha of the invisible clock. - if (mCurrentClockSize == LARGE) { - excludedViews.add(mClockFrame); - } else { - excludedViews.add(mLargeClockFrame); - } - - setChildrenAlphaExcluding(alpha, excludedViews); - } - - /** Sets an alpha value on every child view except for the views in the provided set. */ - public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) { - for (int i = 0; i < mView.getChildCount(); i++) { - final View child = mView.getChildAt(i); - - if (!excludedViews.contains(child)) { - child.setAlpha(alpha); - } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 1ede76fb1fa4..02776a295359 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -16,7 +16,6 @@ package com.android.keyguard; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Display.DEFAULT_DISPLAY_GROUP; import android.app.Presentation; import android.content.Context; @@ -119,10 +118,9 @@ public class KeyguardDisplayManager { if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display"); return false; } - if (mTmpDisplayInfo.displayGroupId != DEFAULT_DISPLAY_GROUP) { + if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) { if (DEBUG) { - Log.i(TAG, - "Do not show KeyguardPresentation on a non-default group display"); + Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display"); } return false; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 853d7402a1f8..cb3172dabdb1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -47,7 +47,6 @@ public class KeyguardStatusView extends GridLayout { private float mDarkAmount = 0; private int mTextColor; - private float mChildrenAlphaExcludingSmartSpace = 1f; public KeyguardStatusView(Context context) { this(context, null, 0); @@ -95,23 +94,6 @@ public class KeyguardStatusView extends GridLayout { mClockView.setTextColor(blendedTextColor); } - public void setChildrenAlphaExcludingClockView(float alpha) { - setChildrenAlphaExcluding(alpha, Set.of(mClockView)); - } - - /** Sets an alpha value on every view except for the views in the provided set. */ - public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) { - mChildrenAlphaExcludingSmartSpace = alpha; - - for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) { - final View child = mStatusViewContainer.getChildAt(i); - - if (!excludedViews.contains(child)) { - child.setAlpha(alpha); - } - } - } - /** Sets a translationY value on every child view except for the media view. */ public void setChildrenTranslationYExcludingMediaView(float translationY) { setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer)); @@ -128,10 +110,6 @@ public class KeyguardStatusView extends GridLayout { } } - public float getChildrenAlphaExcludingSmartSpace() { - return mChildrenAlphaExcludingSmartSpace; - } - public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardStatusView:"); pw.println(" mDarkAmount: " + mDarkAmount); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 14c9cb2022bc..083f2fe53d17 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -20,7 +20,6 @@ import android.graphics.Rect; import android.util.Slog; import com.android.keyguard.KeyguardClockSwitch.ClockSize; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -49,10 +48,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardClockSwitchController mKeyguardClockSwitchController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; - private final DozeParameters mDozeParameters; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - private final KeyguardStateController mKeyguardStateController; private final Rect mClipBounds = new Rect(); @Inject @@ -64,18 +60,14 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController, DozeParameters dozeParameters, - KeyguardUnlockAnimationController keyguardUnlockAnimationController, ScreenOffAnimationController screenOffAnimationController) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; - mDozeParameters = dozeParameters; - mKeyguardStateController = keyguardStateController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true); - mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; } @Override @@ -87,14 +79,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV protected void onViewAttached() { mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mConfigurationController.addCallback(mConfigurationListener); - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); } @Override protected void onViewDetached() { mKeyguardUpdateMonitor.removeCallback(mInfoCallback); mConfigurationController.removeCallback(mConfigurationListener); - mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback); } /** @@ -148,24 +138,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV */ public void setAlpha(float alpha) { if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { - // If we're capable of performing the SmartSpace shared element transition, and we are - // going to (we're swiping to dismiss vs. bringing up the PIN screen), then fade out - // everything except for the SmartSpace. - if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { - mView.setChildrenAlphaExcludingClockView(alpha); - mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(alpha); - } else if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { - // Otherwise, we can just set the alpha for the entire container. - mView.setAlpha(alpha); - - // If we previously unlocked with the shared element transition, some child views - // might still have alpha = 0f. Set them back to 1f since we're just using the - // parent container's alpha. - if (mView.getChildrenAlphaExcludingSmartSpace() < 1f) { - mView.setChildrenAlphaExcludingClockView(1f); - mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f); - } - } + mView.setAlpha(alpha); } } @@ -289,19 +262,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } }; - private KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - // If we explicitly re-show the keyguard, make sure that all the child views are - // visible. They might have been animating out as part of the SmartSpace shared - // element transition. - if (mKeyguardStateController.isShowing()) { - mView.setChildrenAlphaExcludingClockView(1f); - } - } - }; - /** * Rect that specifies how KSV should be clipped, on its parent's coordinates. */ diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index eb6705a2e979..f925eaa0e40b 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -15,8 +15,6 @@ */ package com.android.keyguard; -import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset; - import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -154,7 +152,7 @@ class NumPadAnimator { ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); TypedArray a = ctw.obtainStyledAttributes(customAttrs); - mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0, + mNormalColor = Utils.getPrivateAttrColorIfUnset(ctw, a, 0, 0, com.android.internal.R.attr.colorSurface); mHighlightColor = a.getColor(1, 0); a.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index ccb5b1146a1c..e51a63fbcd10 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -228,14 +228,20 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { if (pendingRotationChange) { return } + val m = Matrix() + // Apply display ratio. + val physicalPixelDisplaySizeRatio = getPhysicalPixelDisplaySizeRatio() + m.postScale(physicalPixelDisplaySizeRatio, physicalPixelDisplaySizeRatio) + + // Apply rotation. val lw: Int = displayInfo.logicalWidth val lh: Int = displayInfo.logicalHeight val flipped = (displayInfo.rotation == Surface.ROTATION_90 || displayInfo.rotation == Surface.ROTATION_270) val dw = if (flipped) lh else lw val dh = if (flipped) lw else lh - val m = Matrix() transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m) + if (!protectionPathOrig.isEmpty) { // Reset the protection path so we don't aggregate rotations protectionPath.set(protectionPathOrig) @@ -244,6 +250,14 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { } } + @VisibleForTesting + open fun getPhysicalPixelDisplaySizeRatio(): Float { + displayInfo.displayCutout?.let { + return it.cutoutPathParserInfo.physicalPixelDisplaySizeRatio + } + return 1f + } + private fun displayModeChanged(oldMode: Display.Mode?, newMode: Display.Mode?): Boolean { if (oldMode == null) { return true @@ -265,17 +279,17 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { out: Matrix ) { when (rotation) { - Surface.ROTATION_0 -> out.reset() + Surface.ROTATION_0 -> return Surface.ROTATION_90 -> { - out.setRotate(270f) + out.postRotate(270f) out.postTranslate(0f, physicalWidth.toFloat()) } Surface.ROTATION_180 -> { - out.setRotate(180f) + out.postRotate(180f) out.postTranslate(physicalWidth.toFloat(), physicalHeight.toFloat()) } Surface.ROTATION_270 -> { - out.setRotate(90f) + out.postRotate(90f) out.postTranslate(physicalHeight.toFloat(), 0f) } else -> throw IllegalArgumentException("Unknown rotation: $rotation") diff --git a/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java b/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java index 35a70a5ed52b..0d1dc9d6a5dd 100644 --- a/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java +++ b/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java @@ -16,6 +16,8 @@ package com.android.systemui; +import android.annotation.StyleRes; +import android.content.res.TypedArray; import android.util.TypedValue; import android.view.View; import android.widget.TextView; @@ -23,9 +25,9 @@ import android.widget.TextView; /** * Utility class to update the font size when the configuration has changed. */ -public class FontSizeUtils { +public final class FontSizeUtils { - public static final float LARGE_TEXT_SCALE = 1.3f; + private FontSizeUtils() {} public static void updateFontSize(View parent, int viewId, int dimensId) { updateFontSize((TextView) parent.findViewById(viewId), dimensId); @@ -37,4 +39,20 @@ public class FontSizeUtils { v.getResources().getDimensionPixelSize(dimensId)); } } + + /** + * Updates the font size according to the style given. + * + * @param v Text to update. + * @param resId Style applying to the text. + */ + public static void updateFontSizeFromStyle(TextView v, @StyleRes int resId) { + int[] attrs = {android.R.attr.textSize}; + int indexOfAttrTextSize = 0; + TypedArray ta = v.getContext().obtainStyledAttributes(resId, attrs); + int updatedTextPixelSize = ta.getDimensionPixelSize(indexOfAttrTextSize, + (int) v.getTextSize()); + v.setTextSize(TypedValue.COMPLEX_UNIT_PX, updatedTextPixelSize); + ta.recycle(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 3641e1d52144..0df2730a48eb 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -380,7 +380,7 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec ) { if (hasTopRoundedCorner == hasTop && hasBottomRoundedCorner == hasBottom && - roundedCornerBottomSize == bottomSize && + roundedCornerTopSize == topSize && roundedCornerBottomSize == bottomSize) { return } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index b98fc03e3acd..8d6509874776 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -348,7 +348,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @Override public void onDisplayChanged(int displayId) { final int newRotation = mContext.getDisplay().getRotation(); - if (mOverlays != null && mRotation != newRotation) { + if ((mOverlays != null || mScreenDecorHwcWindow != null) + && mRotation != newRotation) { // 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 @@ -362,11 +363,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab + mRotation); } - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] != null) { - final ViewGroup overlayView = mOverlays[i].getRootView(); - overlayView.getViewTreeObserver().addOnPreDrawListener( - new RestartingPreDrawListener(overlayView, i, newRotation)); + if (mOverlays != null) { + for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { + if (mOverlays[i] != null) { + final ViewGroup overlayView = mOverlays[i].getRootView(); + overlayView.getViewTreeObserver().addOnPreDrawListener( + new RestartingPreDrawListener(overlayView, i, newRotation)); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt index 9dbeb77ebc00..e316722b64ea 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt @@ -105,7 +105,7 @@ private fun Int.toLayoutGravity(@Surface.Rotation rotation: Int): Int = when (ro DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.BOTTOM DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.LEFT DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.TOP - else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT + else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.RIGHT } Surface.ROTATION_270 -> when (this) { DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.TOP diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index a8c286241141..b21a886b037d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -23,7 +23,7 @@ import android.content.Context import android.graphics.Matrix import android.graphics.Rect import android.os.Handler -import android.provider.Settings +import android.os.RemoteException import android.util.Log import android.view.RemoteAnimationTarget import android.view.SyncRtSurfaceTransactionApplier @@ -47,7 +47,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject -import kotlin.math.min const val TAG = "KeyguardUnlock" @@ -77,7 +76,7 @@ const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f * The dismiss amount is the inverse of the notification panel expansion, which decreases as the * lock screen is swiped away. */ -const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f +const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.15f /** * Dismiss amount at which to complete the keyguard exit animation and hide the keyguard. @@ -85,7 +84,7 @@ const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f * The dismiss amount is the inverse of the notification panel expansion, which decreases as the * lock screen is swiped away. */ -const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f +const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f /** * How long the canned unlock animation takes. This is used if we are unlocking from biometric auth, @@ -112,7 +111,7 @@ const val CANNED_UNLOCK_START_DELAY = 100L * Duration for the alpha animation on the surface behind. This plays to fade in the surface during * a swipe to unlock (and to fade it back out if the swipe is cancelled). */ -const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 150L +const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L /** * Start delay for the surface behind animation, used so that the lockscreen can get out of the way @@ -151,12 +150,21 @@ class KeyguardUnlockAnimationController @Inject constructor( * [playingCannedAnimation] indicates whether we are playing a canned animation to show the * app/launcher behind the keyguard, vs. this being a swipe to unlock where the dismiss * amount drives the animation. + * * [fromWakeAndUnlock] tells us whether we are unlocking directly from AOD - in this case, * the lockscreen is dismissed instantly, so we shouldn't run any animations that rely on it * being visible. + * + * [unlockAnimationStartDelay] and [unlockAnimationDuration] provide the timing parameters + * for the canned animation (if applicable) so interested parties can sync with it. If no + * canned animation is playing, these are both 0. */ @JvmDefault - fun onUnlockAnimationStarted(playingCannedAnimation: Boolean, fromWakeAndUnlock: Boolean) {} + fun onUnlockAnimationStarted( + playingCannedAnimation: Boolean, + fromWakeAndUnlock: Boolean, + unlockAnimationStartDelay: Long, + unlockAnimationDuration: Long) {} /** * Called when the remote unlock animation ends, in all cases, canned or swipe-to-unlock. @@ -165,19 +173,6 @@ class KeyguardUnlockAnimationController @Inject constructor( */ @JvmDefault fun onUnlockAnimationFinished() {} - - /** - * Called when we begin the smartspace shared element transition, either due to an unlock - * action (biometric, etc.) or a swipe to unlock. - * - * This transition can begin BEFORE [onUnlockAnimationStarted] is called, if we are swiping - * to unlock and the surface behind the keyguard has not yet been made visible. This is - * because the lockscreen smartspace immediately begins moving towards the launcher - * smartspace location when a swipe begins, even before we start the keyguard exit remote - * animation and show the launcher itself. - */ - @JvmDefault - fun onSmartspaceSharedElementTransitionStarted() {} } /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ @@ -259,8 +254,9 @@ class KeyguardUnlockAnimationController @Inject constructor( * animation plays. */ private var surfaceBehindAlpha = 1f - private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) - private var smartspaceAnimator = ValueAnimator.ofFloat(0f, 1f) + + @VisibleForTesting + var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) /** * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -281,59 +277,38 @@ class KeyguardUnlockAnimationController @Inject constructor( private var roundedCornerRadius = 0f /** - * Whether we tried to start the SmartSpace shared element transition for this unlock swipe. - * It's possible we were unable to do so (if the Launcher SmartSpace is not available), and we - * need to keep track of that so that we don't start doing it halfway through the swipe if - * Launcher becomes available suddenly. - */ - private var attemptedSmartSpaceTransitionForThisSwipe = false - - /** - * The original location of the lockscreen smartspace on the screen. - */ - private val smartspaceOriginBounds = Rect() - - /** - * The bounds to which the lockscreen smartspace is moving. This is set to the bounds of the - * launcher's smartspace prior to the transition starting. - */ - private val smartspaceDestBounds = Rect() - - /** - * From 0f to 1f, the progress of the smartspace shared element animation. 0f means the - * smartspace is at its normal position within the lock screen hierarchy, and 1f means it has - * fully animated to the location of the Launcher's smartspace. + * Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to + * play the in-window launcher unlock animations rather than simply animating the Launcher + * window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is + * false, if the smartspace is not available or was not ready in time. */ - private var smartspaceUnlockProgress = 0f + private var willUnlockWithInWindowLauncherAnimations: Boolean = false /** - * Whether we're currently unlocking, and we're talking to Launcher to perform in-window - * animations rather than simply animating the Launcher window like any other app. This can be - * true while [unlockingWithSmartspaceTransition] is false, if the smartspace is not available - * or was not ready in time. + * Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to + * play the smartspace shared element animation. If true, + * [willUnlockWithInWindowLauncherAnimations] will also always be true since in-window + * animations are a prerequisite for the smartspace transition. */ - private var unlockingToLauncherWithInWindowAnimations: Boolean = false - - /** - * Whether we are currently unlocking, and the smartspace shared element transition is in - * progress. If true, we're also [unlockingToLauncherWithInWindowAnimations]. - */ - private var unlockingWithSmartspaceTransition: Boolean = false + private var willUnlockWithSmartspaceTransition: Boolean = false private val handler = Handler() init { with(surfaceBehindAlphaAnimator) { duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS - interpolator = Interpolators.TOUCH_RESPONSE + interpolator = Interpolators.LINEAR addUpdateListener { valueAnimator: ValueAnimator -> surfaceBehindAlpha = valueAnimator.animatedValue as Float updateSurfaceBehindAppearAmount() } addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - // If the surface alpha is 0f, it's no longer visible so we can safely be done - // with the animation even if other properties are still animating. + // If we animated the surface alpha to 0f, it means we cancelled a swipe to + // dismiss. In this case, we should ask the KeyguardViewMediator to end the + // remote animation to hide the surface behind the keyguard, but should *not* + // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard + // and unlock the device as well as hiding the surface. if (surfaceBehindAlpha == 0f) { keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation( false /* cancelled */) @@ -360,21 +335,6 @@ class KeyguardUnlockAnimationController @Inject constructor( }) } - with(smartspaceAnimator) { - duration = UNLOCK_ANIMATION_DURATION_MS - interpolator = Interpolators.TOUCH_RESPONSE - addUpdateListener { - smartspaceUnlockProgress = it.animatedValue as Float - } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE) - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( - false /* cancelled */) - } - }) - } - // Listen for changes in the dismiss amount. keyguardStateController.addCallback(this) @@ -394,6 +354,74 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** + * Whether we should be able to do the in-window launcher animations given the current state of + * the device. + */ + fun canPerformInWindowLauncherAnimations(): Boolean { + return isNexusLauncherUnderneath() && + launcherUnlockController != null && + !keyguardStateController.isDismissingFromSwipe && + // Temporarily disable for foldables since foldable launcher has two first pages, + // which breaks the in-window animation. + !isFoldable(context) + } + + /** + * Called from [KeyguardStateController] to let us know that the keyguard going away state has + * changed. + */ + override fun onKeyguardGoingAwayChanged() { + if (keyguardStateController.isKeyguardGoingAway) { + prepareForInWindowLauncherAnimations() + } + } + + /** + * Prepare for in-window Launcher unlock animations, if we're able to do so. + * + * The in-window animations consist of the staggered ring icon unlock animation, and optionally + * the shared element smartspace transition. + */ + fun prepareForInWindowLauncherAnimations() { + willUnlockWithInWindowLauncherAnimations = canPerformInWindowLauncherAnimations() + + if (!willUnlockWithInWindowLauncherAnimations) { + return + } + + // There are additional conditions under which we should not perform the smartspace + // transition specifically, so check those. + willUnlockWithSmartspaceTransition = shouldPerformSmartspaceTransition() + + var lockscreenSmartspaceBounds = Rect() + + // Grab the bounds of our lockscreen smartspace and send them to launcher so they can + // position their smartspace there initially, then animate it to its resting position. + if (willUnlockWithSmartspaceTransition) { + lockscreenSmartspaceBounds = Rect().apply { + lockscreenSmartspace!!.getBoundsOnScreen(this) + offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop) + } + } + + // Currently selected lockscreen smartspace page, or -1 if it's not available. + val selectedPage = + (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1 + + try { + + // Let the launcher know to prepare for this animation. + launcherUnlockController?.prepareForUnlock( + willUnlockWithSmartspaceTransition, /* willAnimateSmartspace */ + lockscreenSmartspaceBounds, /* lockscreenSmartspaceBounds */ + selectedPage, /* selectedPage */ + ) + } catch (e: RemoteException) { + Log.e(TAG, "Remote exception in prepareForInWindowUnlockAnimations.", e) + } + } + + /** * Called from [KeyguardViewMediator] to tell us that the RemoteAnimation on the surface behind * the keyguard has started successfully. We can use these parameters to directly manipulate the * surface for the unlock gesture/animation. @@ -404,7 +432,8 @@ class KeyguardUnlockAnimationController @Inject constructor( * * [requestedShowSurfaceBehindKeyguard] indicates whether the animation started because of a * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture, - * as opposed to being called because the device was unlocked and the keyguard is going away. + * as opposed to being called because the device was unlocked instantly by some other means + * (fingerprint, tap, etc.) and the keyguard is going away. */ fun notifyStartSurfaceBehindRemoteAnimation( target: RemoteAnimationTarget, @@ -422,19 +451,31 @@ class KeyguardUnlockAnimationController @Inject constructor( surfaceBehindRemoteAnimationTarget = target surfaceBehindRemoteAnimationStartTime = startTime - // If we specifically requested that the surface behind be made visible, it means we are - // swiping to unlock. In that case, the surface visibility is tied to the dismiss amount, - // and we'll handle that in onKeyguardDismissAmountChanged(). If we didn't request that, the - // keyguard is being dismissed for a different reason (biometric auth, etc.) and we should - // play a canned animation to make the surface fully visible. - if (!requestedShowSurfaceBehindKeyguard) { + // If we specifically requested that the surface behind be made visible (vs. it being made + // visible because we're unlocking), then we're in the middle of a swipe-to-unlock touch + // gesture and the surface behind the keyguard should be made visible. + if (requestedShowSurfaceBehindKeyguard) { + // Fade in the surface, as long as we're not now flinging. The touch gesture ending in + // a fling during the time it takes the keyguard exit animation to start is an edge + // case race condition, and we'll handle it by playing a canned animation on the + // now-visible surface to finish unlocking. + if (!keyguardStateController.isFlingingToDismissKeyguard) { + fadeInSurfaceBehind() + } else { + playCannedUnlockAnimation() + } + } else { + // The surface was made visible since we're unlocking not from a swipe (fingerprint, + // lock icon long-press, etc). Play the full unlock animation. playCannedUnlockAnimation() } listeners.forEach { it.onUnlockAnimationStarted( playingCannedUnlockAnimation /* playingCannedAnimation */, - biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */) } + biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */, + CANNED_UNLOCK_START_DELAY /* unlockStartDelay */, + LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) } // Finish the keyguard remote animation if the dismiss amount has crossed the threshold. // Check it here in case there is no more change to the dismiss amount after the last change @@ -443,57 +484,32 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** - * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and - * we should clean up all of our state. - */ - fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) { - // Cancel any pending actions. - handler.removeCallbacksAndMessages(null) - - // Make sure we made the surface behind fully visible, just in case. It should already be - // fully visible. - setSurfaceBehindAppearAmount(1f) - launcherUnlockController?.setUnlockAmount(1f) - smartspaceDestBounds.setEmpty() - - // That target is no longer valid since the animation finished, null it out. - surfaceBehindRemoteAnimationTarget = null - surfaceBehindParams = null - - playingCannedUnlockAnimation = false - unlockingToLauncherWithInWindowAnimations = false - unlockingWithSmartspaceTransition = false - resetSmartspaceTransition() - - listeners.forEach { it.onUnlockAnimationFinished() } - } - - /** * Play a canned unlock animation to unlock the device. This is used when we were *not* swiping * to unlock using a touch gesture. If we were swiping to unlock, the animation will be driven * by the dismiss amount via [onKeyguardDismissAmountChanged]. */ - fun playCannedUnlockAnimation() { + private fun playCannedUnlockAnimation() { playingCannedUnlockAnimation = true - if (canPerformInWindowLauncherAnimations()) { - // If possible, use the neat in-window animations to unlock to the launcher. - unlockToLauncherWithInWindowAnimations() - } else if (!biometricUnlockControllerLazy.get().isWakeAndUnlock) { - // If the launcher isn't behind the keyguard, or the launcher unlock controller is not - // available, animate in the entire window. - surfaceBehindEntryAnimator.start() - } else { - setSurfaceBehindAppearAmount(1f) - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false) - } - // If this is a wake and unlock, hide the lockscreen immediately. In the future, we should - // animate it out nicely instead, but to the current state of wake and unlock, not hiding it - // causes a lot of issues. - // TODO(b/210016643): Not this, it looks not-ideal! - if (biometricUnlockControllerLazy.get().isWakeAndUnlock) { - keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350) + when { + // If we're set up for in-window launcher animations, ask Launcher to play its in-window + // canned animation. + willUnlockWithInWindowLauncherAnimations -> unlockToLauncherWithInWindowAnimations() + + // If we're waking and unlocking to a non-Launcher app surface (or Launcher in-window + // animations are not available), show it immediately and end the remote animation. The + // circular light reveal will show the app surface, and it looks weird if it's moving + // around behind that. + biometricUnlockControllerLazy.get().isWakeAndUnlock -> { + setSurfaceBehindAppearAmount(1f) + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } + + // Otherwise, we're doing a normal full-window unlock. Start this animator, which will + // scale/translate the window underneath the lockscreen. + else -> surfaceBehindEntryAnimator.start() } } @@ -502,205 +518,31 @@ class KeyguardUnlockAnimationController @Inject constructor( * transition if possible. */ private fun unlockToLauncherWithInWindowAnimations() { - // See if we can do the smartspace transition, and if so, do it! - if (prepareForSmartspaceTransition()) { - animateSmartspaceToDestination() - listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() } - } - - val startDelay = Settings.Secure.getLong( - context.contentResolver, "unlock_start_delay", CANNED_UNLOCK_START_DELAY) - val duration = Settings.Secure.getLong( - context.contentResolver, "unlock_duration", LAUNCHER_ICONS_ANIMATION_DURATION_MS) - - unlockingToLauncherWithInWindowAnimations = true - prepareLauncherWorkspaceForUnlockAnimation() + setSurfaceBehindAppearAmount(1f) // Begin the animation, waiting for the shade to animate out. launcherUnlockController?.playUnlockAnimation( true /* unlocked */, - duration /* duration */, - startDelay /* startDelay */) - - handler.postDelayed({ - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withAlpha(1f) - .build()) - }, startDelay) - - if (!unlockingWithSmartspaceTransition) { - // If we are not unlocking with the smartspace transition, wait for the unlock animation - // to end and then finish the remote animation. If we are using the smartspace - // transition, it will finish the remote animation once it ends. - handler.postDelayed({ - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( - false /* cancelled */) - }, UNLOCK_ANIMATION_DURATION_MS) - } - } - - /** - * Asks Launcher to prepare the workspace to be unlocked. This sets up the animation and makes - * the page invisible. - */ - private fun prepareLauncherWorkspaceForUnlockAnimation() { - // Tell the launcher to prepare for the animation by setting its views invisible and - // syncing the selected smartspace pages. - launcherUnlockController?.prepareForUnlock( - unlockingWithSmartspaceTransition /* willAnimateSmartspace */, - (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1) - } - - /** - * Animates the lockscreen smartspace all the way to the launcher's smartspace location, then - * makes the launcher smartspace visible and ends the remote animation. - */ - private fun animateSmartspaceToDestination() { - smartspaceAnimator.start() - } - - /** - * Reset the lockscreen smartspace's position, and reset all state involving the smartspace - * transition. - */ - public fun resetSmartspaceTransition() { - unlockingWithSmartspaceTransition = false - smartspaceUnlockProgress = 0f - - lockscreenSmartspace?.post { - lockscreenSmartspace!!.translationX = 0f - lockscreenSmartspace!!.translationY = 0f - } - } - - /** - * Moves the lockscreen smartspace towards the launcher smartspace's position. - */ - private fun setSmartspaceProgressToDestinationBounds(progress: Float) { - if (smartspaceDestBounds.isEmpty) { - return - } - - val progressClamped = min(1f, progress) - - // Calculate the distance (relative to the origin) that we need to be for the current - // progress value. - val progressX = - (smartspaceDestBounds.left - smartspaceOriginBounds.left) * progressClamped - val progressY = - (smartspaceDestBounds.top - smartspaceOriginBounds.top) * progressClamped - - val lockscreenSmartspaceCurrentBounds = Rect().also { - lockscreenSmartspace!!.getBoundsOnScreen(it) - } - - // Figure out how far that is from our present location on the screen. This approach - // compensates for the fact that our parent container is also translating to animate out. - val dx = smartspaceOriginBounds.left + progressX - - lockscreenSmartspaceCurrentBounds.left - val dy = smartspaceOriginBounds.top + progressY - - lockscreenSmartspaceCurrentBounds.top - - with(lockscreenSmartspace!!) { - translationX += dx - translationY += dy - } - } - - /** - * Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As - * the dismiss amount increases, we will increase our SmartSpace's progress to the destination - * bounds (the location of the Launcher SmartSpace). - * - * This is used by [KeyguardClockSwitchController] to keep the smartspace position updated as - * the clock is swiped away. - */ - fun updateLockscreenSmartSpacePosition() { - setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress) - } - - /** - * Asks the keyguard view to hide, using the start time from the beginning of the remote - * animation. - */ - fun hideKeyguardViewAfterRemoteAnimation() { - if (keyguardViewController.isShowing) { - // Hide the keyguard, with no fade out since we animated it away during the unlock. - keyguardViewController.hide( - surfaceBehindRemoteAnimationStartTime, - 0 /* fadeOutDuration */ - ) - } else { - Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " + - "showing. Ignoring...") - } - } + LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */, + CANNED_UNLOCK_START_DELAY /* startDelay */) - private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { - surfaceTransactionApplier!!.scheduleApply(params) - surfaceBehindParams = params - } + // Now that the Launcher surface (with its smartspace positioned identically to ours) is + // visible, hide our smartspace. + lockscreenSmartspace!!.visibility = View.INVISIBLE - /** - * Scales in and translates up the surface behind the keyguard. This is used during unlock - * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is - * cancelled). - */ - fun setSurfaceBehindAppearAmount(amount: Float) { - if (surfaceBehindRemoteAnimationTarget == null) { - return - } - - if (unlockingToLauncherWithInWindowAnimations) { - // If we aren't using the canned unlock animation (which would be setting the unlock - // amount in its update listener), do it here. - if (!isPlayingCannedUnlockAnimation()) { - launcherUnlockController?.setUnlockAmount(amount) - - if (surfaceBehindParams?.alpha?.let { it < 1f } != false) { - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withAlpha(1f) - .build()) - } + // As soon as the shade has animated out of the way, finish the keyguard exit animation. The + // in-window animations in the Launcher window will end on their own. + handler.postDelayed({ + if (keyguardViewMediator.get().isShowingAndNotOccluded && + !keyguardStateController.isKeyguardGoingAway) { + Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " + + "showing and not going away.") + return@postDelayed } - } else { - // Otherwise, animate in the surface's scale/transltion. - val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() - val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + - (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * - MathUtils.clamp(amount, 0f, 1f)) - - // Scale up from a point at the center-bottom of the surface. - surfaceBehindMatrix.setScale( - scaleFactor, - scaleFactor, - surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, - surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y - ) - // Translate up from the bottom. - surfaceBehindMatrix.postTranslate( - 0f, - surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) - ) - - // If we're snapping the keyguard back, immediately begin fading it out. - val animationAlpha = - if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount - else surfaceBehindAlpha - - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withMatrix(surfaceBehindMatrix) - .withCornerRadius(roundedCornerRadius) - .withAlpha(animationAlpha) - .build()) - } + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + }, CANNED_UNLOCK_START_DELAY) } /** @@ -738,7 +580,7 @@ class KeyguardUnlockAnimationController @Inject constructor( return } - if (keyguardViewController.isShowing) { + if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) { showOrHideSurfaceIfDismissAmountThresholdsReached() // If the surface is visible or it's about to be, start updating its appearance to @@ -750,11 +592,6 @@ class KeyguardUnlockAnimationController @Inject constructor( updateSurfaceBehindAppearAmount() } } - - // The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell - // Launcher's SmartSpace to become visible again), so update it even if the keyguard view is - // no longer showing. - applyDismissAmountToSmartspaceTransition() } /** @@ -775,18 +612,16 @@ class KeyguardUnlockAnimationController @Inject constructor( return } + if (!keyguardStateController.isShowing) { + return + } + val dismissAmount = keyguardStateController.dismissAmount + if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && - !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { - // We passed the threshold, and we're not yet showing the surface behind the - // keyguard. Animate it in. - if (!unlockingToLauncherWithInWindowAnimations && - canPerformInWindowLauncherAnimations()) { - unlockingToLauncherWithInWindowAnimations = true - prepareLauncherWorkspaceForUnlockAnimation() - } + !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { + keyguardViewMediator.get().showSurfaceBehindKeyguard() - fadeInSurfaceBehind() } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { // We're no longer past the threshold but we are showing the surface. Animate it @@ -828,60 +663,103 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** - * Updates flags related to the SmartSpace transition in response to a change in keyguard - * dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher - * know if it needs to do something as a result. + * Scales in and translates up the surface behind the keyguard. This is used during unlock + * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is + * cancelled). */ - private fun applyDismissAmountToSmartspaceTransition() { - if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) { + fun setSurfaceBehindAppearAmount(amount: Float) { + if (surfaceBehindRemoteAnimationTarget == null) { return } - // If we are playing the canned animation, the smartspace is being animated directly between - // its original location and the location of the launcher smartspace by smartspaceAnimator. - // We can ignore the dismiss amount, which is caused by panel height changes as the panel is - // flung away. - if (playingCannedUnlockAnimation) { - return - } + // Otherwise, animate in the surface's scale/transltion. + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() + val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.setScale( + scaleFactor, + scaleFactor, + surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y + ) + + // Translate up from the bottom. + surfaceBehindMatrix.postTranslate( + 0f, + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) + ) + + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = + if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount + else surfaceBehindAlpha + + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget!!.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build()) + } - val dismissAmount = keyguardStateController.dismissAmount + /** + * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and + * we should clean up all of our state. + * + * This is generally triggered by us, calling + * [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation]. + */ + fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) { + // Cancel any pending actions. + handler.removeCallbacksAndMessages(null) - // If we've begun a swipe, and haven't yet tried doing the SmartSpace transition, do that - // now. - if (!attemptedSmartSpaceTransitionForThisSwipe && - keyguardViewController.isShowing && - dismissAmount > 0f && - dismissAmount < 1f) { - attemptedSmartSpaceTransitionForThisSwipe = true + // Make sure we made the surface behind fully visible, just in case. It should already be + // fully visible. If the launcher is doing its own animation, let it continue without + // forcing it to 1f. + setSurfaceBehindAppearAmount(1f) + launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) - if (prepareForSmartspaceTransition()) { - unlockingWithSmartspaceTransition = true + // That target is no longer valid since the animation finished, null it out. + surfaceBehindRemoteAnimationTarget = null + surfaceBehindParams = null - // Ensure that the smartspace is invisible if we're doing the transition, and - // visible if we aren't. - launcherUnlockController?.setSmartspaceVisibility( - if (unlockingWithSmartspaceTransition) View.INVISIBLE else View.VISIBLE) + playingCannedUnlockAnimation = false + willUnlockWithInWindowLauncherAnimations = false + willUnlockWithSmartspaceTransition = false - if (unlockingWithSmartspaceTransition) { - listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() } - } - } - } else if (attemptedSmartSpaceTransitionForThisSwipe && - (dismissAmount == 0f || dismissAmount == 1f)) { - attemptedSmartSpaceTransitionForThisSwipe = false - unlockingWithSmartspaceTransition = false - launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE) - } + // The lockscreen surface is gone, so it is now safe to re-show the smartspace. + lockscreenSmartspace?.visibility = View.VISIBLE + + listeners.forEach { it.onUnlockAnimationFinished() } + } + + /** + * Asks the keyguard view to hide, using the start time from the beginning of the remote + * animation. + */ + fun hideKeyguardViewAfterRemoteAnimation() { + if (keyguardViewController.isShowing) { + // Hide the keyguard, with no fade out since we animated it away during the unlock. - if (unlockingWithSmartspaceTransition) { - val swipedFraction: Float = keyguardStateController.dismissAmount - val progress = swipedFraction / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - smartspaceUnlockProgress = progress - setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress) + keyguardViewController.hide( + surfaceBehindRemoteAnimationStartTime, + 0 /* fadeOutDuration */ + ) + } else { + Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " + + "showing. Ignoring...") } } + private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { + surfaceTransactionApplier!!.scheduleApply(params) + surfaceBehindParams = params + } + private fun fadeInSurfaceBehind() { surfaceBehindAlphaAnimator.cancel() surfaceBehindAlphaAnimator.start() @@ -892,14 +770,8 @@ class KeyguardUnlockAnimationController @Inject constructor( surfaceBehindAlphaAnimator.reverse() } - /** - * Prepare for the smartspace shared element transition, if possible, by figuring out where we - * are animating from/to. - * - * Return true if we'll be able to do the smartspace transition, or false if conditions are not - * right to do it right now. - */ - private fun prepareForSmartspaceTransition(): Boolean { + + private fun shouldPerformSmartspaceTransition(): Boolean { // Feature is disabled, so we don't want to. if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) { return false @@ -940,45 +812,22 @@ class KeyguardUnlockAnimationController @Inject constructor( return false } - unlockingWithSmartspaceTransition = true - smartspaceDestBounds.setEmpty() - - // Assuming we were able to retrieve the launcher's state, start the lockscreen - // smartspace at 0, 0, and save its starting bounds. - with(lockscreenSmartspace!!) { - translationX = 0f - translationY = 0f - getBoundsOnScreen(smartspaceOriginBounds) - } - - // Set the destination bounds to the launcher smartspace's bounds, offset by any - // padding on our smartspace. - with(smartspaceDestBounds) { - set(launcherSmartspaceState!!.boundsOnScreen) - offset(-lockscreenSmartspace!!.paddingLeft, -lockscreenSmartspace!!.paddingTop) + // We started to swipe to dismiss, but now we're doing a fling animation to complete the + // dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't + // do the shared element transition. + if (keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture) { + return false } return true } /** - * Whether we should be able to do the in-window launcher animations given the current state of - * the device. - */ - fun canPerformInWindowLauncherAnimations(): Boolean { - return isNexusLauncherUnderneath() && - launcherUnlockController != null && - // Temporarily disable for foldables since foldable launcher has two first pages, - // which breaks the in-window animation. - !isFoldable(context) - } - - /** * Whether we are currently in the process of unlocking the keyguard, and we are performing the * shared element SmartSpace transition. */ fun isUnlockingWithSmartSpaceTransition(): Boolean { - return unlockingWithSmartspaceTransition + return willUnlockWithSmartspaceTransition } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 29e940f24df6..10ea1e06c6d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -17,6 +17,8 @@ package com.android.systemui.keyguard; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN; @@ -119,7 +121,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -2307,8 +2308,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) { - flags |= WindowManagerPolicyConstants - .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; + flags |= KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade() || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) { @@ -2324,6 +2324,15 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, .KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS; } + // If we are unlocking to the launcher, clear the snapshot so that any changes as part + // of the in-window animations are reflected. This is needed even if we're not actually + // playing in-window animations for this particular unlock since a previous unlock might + // have changed the Launcher state. + if (mWakeAndUnlocking + && KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) { + flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; + } + mUpdateMonitor.setKeyguardGoingAway(true); mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true); @@ -2628,9 +2637,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSurfaceBehindRemoteAnimationRequested = true; try { - ActivityTaskManager.getService().keyguardGoingAway( - WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS - | WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER); + int flags = KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS + | KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; + + // If we are unlocking to the launcher, clear the snapshot so that any changes as part + // of the in-window animations are reflected. This is needed even if we're not actually + // playing in-window animations for this particular unlock since a previous unlock might + // have changed the Launcher state. + if (KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) { + flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; + } + + ActivityTaskManager.getService().keyguardGoingAway(flags); mKeyguardStateController.notifyKeyguardGoingAway(true); } catch (RemoteException e) { mSurfaceBehindRemoteAnimationRequested = false; @@ -2660,7 +2678,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */ - public void finishSurfaceBehindRemoteAnimation(boolean cancelled) { + void finishSurfaceBehindRemoteAnimation(boolean cancelled) { if (!mSurfaceBehindRemoteAnimationRunning) { return; } @@ -2796,12 +2814,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; - // We're going to animate in the Launcher, so ask WM to clear the task snapshot so we don't - // initially display an old snapshot with all of the icons visible. We're System UI, so - // we're allowed to pass in null to ask WM to find the home activity for us to prevent - // needing to IPC to Launcher. - ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(null /* homeActivity */); - keyguardDone(); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index 70052fd45abf..5a214d1cd5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -21,41 +21,24 @@ import android.animation.ValueAnimator.AnimatorUpdateListener import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList -import android.graphics.drawable.GradientDrawable import com.android.internal.R import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.monet.ColorScheme -import com.android.systemui.util.getColorWithAlpha /** - * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme] - * is triggered. - */ -interface ColorTransition { - fun updateColorScheme(scheme: ColorScheme?) -} - -/** A generic implementation of [ColorTransition] so that we can define a factory method. */ -open class GenericColorTransition( - private val applyTheme: (ColorScheme?) -> Unit -) : ColorTransition { - override fun updateColorScheme(scheme: ColorScheme?) = applyTheme(scheme) -} - -/** - * A [ColorTransition] that animates between two specific colors. + * ColorTransition is responsible for managing the animation between two specific colors. * It uses a ValueAnimator to execute the animation and interpolate between the source color and * the target color. * * Selection of the target color from the scheme, and application of the interpolated color * are delegated to callbacks. */ -open class AnimatingColorTransition( +open class ColorTransition( private val defaultColor: Int, private val extractColor: (ColorScheme) -> Int, private val applyColor: (Int) -> Unit -) : AnimatorUpdateListener, ColorTransition { +) : AnimatorUpdateListener { private val argbEvaluator = ArgbEvaluator() private val valueAnimator = buildAnimator() @@ -70,7 +53,7 @@ open class AnimatingColorTransition( applyColor(currentColor) } - override fun updateColorScheme(scheme: ColorScheme?) { + fun updateColorScheme(scheme: ColorScheme?) { val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme) if (newTargetColor != targetColor) { sourceColor = currentColor @@ -93,9 +76,7 @@ open class AnimatingColorTransition( } } -typealias AnimatingColorTransitionFactory = - (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition -typealias GenericColorTransitionFactory = ((ColorScheme?) -> Unit) -> GenericColorTransition +typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> ColorTransition /** * ColorSchemeTransition constructs a ColorTransition for each color in the scheme @@ -105,26 +86,27 @@ typealias GenericColorTransitionFactory = ((ColorScheme?) -> Unit) -> GenericCol class ColorSchemeTransition internal constructor( private val context: Context, mediaViewHolder: MediaViewHolder, - animatingColorTransitionFactory: AnimatingColorTransitionFactory, - genericColorTransitionFactory: GenericColorTransitionFactory + colorTransitionFactory: ColorTransitionFactory ) { constructor(context: Context, mediaViewHolder: MediaViewHolder) : - this(context, mediaViewHolder, ::AnimatingColorTransition, ::GenericColorTransition) + this(context, mediaViewHolder, ::ColorTransition) val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) - val surfaceColor = animatingColorTransitionFactory( + val surfaceColor = colorTransitionFactory( bgColor, ::surfaceFromScheme ) { surfaceColor -> val colorList = ColorStateList.valueOf(surfaceColor) mediaViewHolder.player.backgroundTintList = colorList + mediaViewHolder.albumView.foregroundTintList = colorList + mediaViewHolder.albumView.backgroundTintList = colorList mediaViewHolder.seamlessIcon.imageTintList = colorList mediaViewHolder.seamlessText.setTextColor(surfaceColor) mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) } - val accentPrimary = animatingColorTransitionFactory( + val accentPrimary = colorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::accentPrimaryFromScheme ) { accentPrimary -> @@ -134,7 +116,7 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) } - val textPrimary = animatingColorTransitionFactory( + val textPrimary = colorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::textPrimaryFromScheme ) { textPrimary -> @@ -150,65 +132,28 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) } - val textPrimaryInverse = animatingColorTransitionFactory( + val textPrimaryInverse = colorTransitionFactory( loadDefaultColor(R.attr.textColorPrimaryInverse), ::textPrimaryInverseFromScheme ) { textPrimaryInverse -> mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse) } - val textSecondary = animatingColorTransitionFactory( + val textSecondary = colorTransitionFactory( loadDefaultColor(R.attr.textColorSecondary), ::textSecondaryFromScheme ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) } - val textTertiary = animatingColorTransitionFactory( + val textTertiary = colorTransitionFactory( loadDefaultColor(R.attr.textColorTertiary), ::textTertiaryFromScheme ) { textTertiary -> mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary) } - // Note: This background gradient currently doesn't animate between colors. - val backgroundGradient = genericColorTransitionFactory { scheme -> - val defaultTintColor = ColorStateList.valueOf(bgColor) - if (scheme == null) { - mediaViewHolder.albumView.foregroundTintList = defaultTintColor - mediaViewHolder.albumView.backgroundTintList = defaultTintColor - return@genericColorTransitionFactory - } - - // If there's no album art, just hide the gradient so we show the solid background. - val showGradient = mediaViewHolder.albumView.drawable != null - val startColor = getColorWithAlpha( - backgroundStartFromScheme(scheme), - alpha = if (showGradient) .25f else 0f - ) - val endColor = getColorWithAlpha( - backgroundEndFromScheme(scheme), - alpha = if (showGradient) .90f else 0f - ) - val gradientColors = intArrayOf(startColor, endColor) - - val foregroundGradient = mediaViewHolder.albumView.foreground.mutate() - if (foregroundGradient is GradientDrawable) { - foregroundGradient.colors = gradientColors - } - val backgroundGradient = mediaViewHolder.albumView.background.mutate() - if (backgroundGradient is GradientDrawable) { - backgroundGradient.colors = gradientColors - } - } - val colorTransitions = arrayOf( - surfaceColor, - accentPrimary, - textPrimary, - textPrimaryInverse, - textSecondary, - textTertiary, - backgroundGradient - ) + surfaceColor, accentPrimary, textPrimary, + textPrimaryInverse, textSecondary, textTertiary) private fun loadDefaultColor(id: Int): Int { return Utils.getColorAttr(context, id).defaultColor diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt index 5e767b0458b9..97c6014c91bd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt @@ -35,9 +35,3 @@ internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] / /** Returns the tertiary text color for media controls based on the scheme. */ internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400 - -/** Returns the color for the start of the background gradient based on the scheme. */ -internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700 - -/** Returns the color for the end of the background gradient based on the scheme. */ -internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700 diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt new file mode 100644 index 000000000000..31266b6dc8ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt @@ -0,0 +1,73 @@ +/* + * 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.media.dialog + +import android.content.Context +import android.media.session.MediaSessionManager +import android.view.View +import com.android.internal.logging.UiEventLogger +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.media.nearby.NearbyMediaDevicesManager +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import java.util.Optional +import javax.inject.Inject + +/** + * Factory to create [MediaOutputBroadcastDialog] objects. + */ +class MediaOutputBroadcastDialogFactory @Inject constructor( + private val context: Context, + private val mediaSessionManager: MediaSessionManager, + private val lbm: LocalBluetoothManager?, + private val starter: ActivityStarter, + private val broadcastSender: BroadcastSender, + private val notifCollection: CommonNotifCollection, + private val uiEventLogger: UiEventLogger, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager> +) { + var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null + + /** Creates a [MediaOutputBroadcastDialog] for the given package. */ + fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) { + // Dismiss the previous dialog, if any. + mediaOutputBroadcastDialog?.dismiss() + + val controller = MediaOutputController(context, packageName, + mediaSessionManager, lbm, starter, notifCollection, + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional) + val dialog = + MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) + mediaOutputBroadcastDialog = dialog + + // Show the dialog. + if (view != null) { + dialogLaunchAnimator.showFromView(dialog, view) + } else { + dialog.show() + } + } + + /** dismiss [MediaOutputBroadcastDialog] if exist. */ + fun dismiss() { + mediaOutputBroadcastDialog?.dismiss() + mediaOutputBroadcastDialog = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt index 7fb7d8b0eaa5..dd9d35bf2021 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt @@ -31,7 +31,8 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) * BroadcastReceiver for handling media output intent */ class MediaOutputDialogReceiver @Inject constructor( - private val mediaOutputDialogFactory: MediaOutputDialogFactory + private val mediaOutputDialogFactory: MediaOutputDialogFactory, + private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, @@ -43,6 +44,16 @@ class MediaOutputDialogReceiver @Inject constructor( } else if (DEBUG) { Log.e(TAG, "Unable to launch media output dialog. Package name is empty.") } + } else if (TextUtils.equals( + MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG, + intent.action)) { + val packageName: String? = + intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME) + if (!TextUtils.isEmpty(packageName)) { + mediaOutputBroadcastDialogFactory.create(packageName!!, false) + } else if (DEBUG) { + Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.") + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index fe4cb7182168..d4e164208167 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -67,7 +67,7 @@ class PrivacyDialog( attributes.receiveInsetsIgnoringZOrder = true setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) } - + setTitle(R.string.ongoing_privacy_dialog_a11y_title) setContentView(R.layout.privacy_dialog) rootView = requireViewById<ViewGroup>(R.id.root) diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java index 2959c3b30eec..592da6554b90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.carrier; +import android.annotation.StyleRes; import android.content.Context; import android.content.res.ColorStateList; import android.text.TextUtils; @@ -30,6 +31,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import java.util.Objects; @@ -146,4 +148,8 @@ public class QSCarrier extends LinearLayout { public void setCarrierText(CharSequence text) { mCarrierText.setText(text); } + + public void updateTextAppearance(@StyleRes int resId) { + FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java index d03563ffb342..a36035b99b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java @@ -16,12 +16,14 @@ package com.android.systemui.qs.carrier; +import android.annotation.StyleRes; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; /** @@ -55,4 +57,11 @@ public class QSCarrierGroup extends LinearLayout { View getCarrierDivider2() { return findViewById(R.id.qs_carrier_divider2); } + + public void updateTextAppearance(@StyleRes int resId) { + FontSizeUtils.updateFontSizeFromStyle(getNoSimTextView(), resId); + getCarrier1View().updateTextAppearance(resId); + getCarrier2View().updateTextAppearance(resId); + getCarrier3View().updateTextAppearance(resId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 8ca095d9a609..6eb54f799a24 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -24,7 +24,6 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.telephony.ServiceState; @@ -90,8 +89,6 @@ public class InternetDialog extends SystemUIDialog implements @VisibleForTesting protected InternetAdapter mAdapter; @VisibleForTesting - protected WifiManager mWifiManager; - @VisibleForTesting protected View mDialogView; @VisibleForTesting protected boolean mCanConfigWifi; @@ -179,7 +176,6 @@ public class InternetDialog extends SystemUIDialog implements mSubscriptionManager = mInternetDialogController.getSubscriptionManager(); mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId(); mTelephonyManager = mInternetDialogController.getTelephonyManager(); - mWifiManager = mInternetDialogController.getWifiManager(); mCanConfigMobileData = canConfigMobileData; mCanConfigWifi = canConfigWifi; mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context); @@ -332,7 +328,7 @@ public class InternetDialog extends SystemUIDialog implements showProgressBar(); final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked(); - final boolean isWifiEnabled = mWifiManager != null && mWifiManager.isWifiEnabled(); + final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled(); updateWifiToggle(isWifiEnabled, isDeviceLocked); updateConnectedWifi(isWifiEnabled, isDeviceLocked); @@ -362,9 +358,8 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { - if (mWifiManager == null) return; - buttonView.setChecked(isChecked); - mWifiManager.setWifiEnabled(isChecked); + if (mInternetDialogController.isWifiEnabled() == isChecked) return; + mInternetDialogController.setWifiEnabled(isChecked); }); mDoneButton.setOnClickListener(v -> dismiss()); mAirplaneModeButton.setOnClickListener(v -> { @@ -388,7 +383,7 @@ public class InternetDialog extends SystemUIDialog implements Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive); } - boolean isWifiEnabled = mWifiManager != null && mWifiManager.isWifiEnabled(); + boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); if (!mInternetDialogController.hasActiveSubId() && (!isWifiEnabled || !isCarrierNetworkActive)) { mMobileNetworkLayout.setVisibility(View.GONE); @@ -444,7 +439,9 @@ public class InternetDialog extends SystemUIDialog implements @MainThread private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) { - mWiFiToggle.setChecked(isWifiEnabled); + if (mWiFiToggle.isChecked() != isWifiEnabled) { + mWiFiToggle.setChecked(isWifiEnabled); + } if (isDeviceLocked) { mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null) ? R.style.TextAppearance_InternetDialog_Active @@ -572,7 +569,7 @@ public class InternetDialog extends SystemUIDialog implements } protected void showProgressBar() { - if (mWifiManager == null || !mWifiManager.isWifiEnabled() + if (!mInternetDialogController.isWifiEnabled() || mInternetDialogController.isDeviceLocked()) { setProgressBarVisible(false); return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index d97ce7757d8c..90a3d4586fd3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -22,6 +22,7 @@ import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.annotation.AnyThread; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -157,6 +158,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi private LocationController mLocationController; private DialogLaunchAnimator mDialogLaunchAnimator; private boolean mHasWifiEntries; + private WifiStateWorker mWifiStateWorker; @VisibleForTesting static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; @@ -210,7 +212,9 @@ public class InternetDialogController implements AccessPointController.AccessPoi @Background Handler workerHandler, CarrierConfigTracker carrierConfigTracker, LocationController locationController, - DialogLaunchAnimator dialogLaunchAnimator) { + DialogLaunchAnimator dialogLaunchAnimator, + WifiStateWorker wifiStateWorker + ) { if (DEBUG) { Log.d(TAG, "Init InternetDialogController"); } @@ -241,6 +245,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mLocationController = locationController; mDialogLaunchAnimator = dialogLaunchAnimator; mConnectedWifiInternetMonitor = new ConnectedWifiInternetMonitor(); + mWifiStateWorker = wifiStateWorker; } void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) { @@ -323,7 +328,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi @Nullable CharSequence getSubtitleText(boolean isProgressBarVisible) { - if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) { + if (mCanConfigWifi && !isWifiEnabled()) { // When Wi-Fi is disabled. // Sub-Title: Wi-Fi is off if (DEBUG) { @@ -648,6 +653,27 @@ public class InternetDialogController implements AccessPointController.AccessPoi startActivity(intent, view); } + /** + * Enable or disable Wi-Fi. + * + * @param enabled {@code true} to enable, {@code false} to disable. + */ + @AnyThread + public void setWifiEnabled(boolean enabled) { + mWifiStateWorker.setWifiEnabled(enabled); + } + + /** + * Return whether Wi-Fi is enabled or disabled. + * + * @return {@code true} if Wi-Fi is enabled or enabling + * @see WifiManager#getWifiState() + */ + @AnyThread + public boolean isWifiEnabled() { + return mWifiStateWorker.isWifiEnabled(); + } + void connectCarrierNetwork() { final MergedCarrierEntry mergedCarrierEntry = mAccessPointController.getMergedCarrierEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/WifiStateWorker.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/WifiStateWorker.java new file mode 100644 index 000000000000..a7ea50e5e6dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/WifiStateWorker.java @@ -0,0 +1,124 @@ +/* + * 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.dialog; + +import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE; +import static android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.wifi.WifiManager; +import android.util.Log; + +import androidx.annotation.AnyThread; +import androidx.annotation.Nullable; + +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import javax.inject.Inject; + +/** + * Worker for the Wi-Fi enabled state cache. + */ +@SysUISingleton +public class WifiStateWorker extends BroadcastReceiver { + + private static final String TAG = "WifiStateWorker"; + + private DelayableExecutor mBackgroundExecutor; + private WifiManager mWifiManager; + private int mWifiState = WIFI_STATE_DISABLED; + + @Inject + public WifiStateWorker( + BroadcastDispatcher broadcastDispatcher, + @Background DelayableExecutor backgroundExecutor, + @Nullable WifiManager wifiManager) { + mWifiManager = wifiManager; + mBackgroundExecutor = backgroundExecutor; + + broadcastDispatcher.registerReceiver(this, new IntentFilter(WIFI_STATE_CHANGED_ACTION)); + mBackgroundExecutor.execute(() -> { + if (mWifiManager == null) return; + + mWifiState = mWifiManager.getWifiState(); + Log.i(TAG, "WifiManager.getWifiState():" + mWifiState); + }); + } + + /** + * Enable or disable Wi-Fi. + * + * @param enabled {@code true} to enable, {@code false} to disable. + */ + @AnyThread + public void setWifiEnabled(boolean enabled) { + mBackgroundExecutor.execute(() -> { + if (mWifiManager == null) return; + + mWifiState = (enabled) ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING; + if (!mWifiManager.setWifiEnabled(enabled)) { + Log.e(TAG, "Failed to WifiManager.setWifiEnabled(" + enabled + ");"); + } + }); + } + + /** + * Gets the Wi-Fi enabled state. + * + * @return One of {@link WifiManager#WIFI_STATE_DISABLED}, + * {@link WifiManager#WIFI_STATE_DISABLING}, {@link WifiManager#WIFI_STATE_ENABLED}, + * {@link WifiManager#WIFI_STATE_ENABLING} + */ + @AnyThread + public int getWifiState() { + return mWifiState; + } + + /** + * Return whether Wi-Fi is enabled or disabled. + * + * @return {@code true} if Wi-Fi is enabled or enabling + * @see WifiManager#getWifiState() + */ + @AnyThread + public boolean isWifiEnabled() { + return (mWifiState == WIFI_STATE_ENABLED || mWifiState == WIFI_STATE_ENABLING); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) return; + + if (WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { + final int wifiState = intent.getIntExtra(EXTRA_WIFI_STATE, WIFI_STATE_DISABLED); + if (wifiState == WIFI_STATE_UNKNOWN) return; + + mWifiState = wifiState; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 009d4b9b48e6..4728c678f96c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -674,15 +674,21 @@ public class ScreenshotController { if (mLastScrollCaptureRequest != null) { mLastScrollCaptureRequest.cancel(true); } - mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY); + final ListenableFuture<ScrollCaptureResponse> future = + mScrollCaptureClient.request(DEFAULT_DISPLAY); + mLastScrollCaptureRequest = future; mLastScrollCaptureRequest.addListener(() -> - onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor); + onScrollCaptureResponseReady(future), mMainExecutor); } private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) { try { if (mLastScrollCaptureResponse != null) { mLastScrollCaptureResponse.close(); + mLastScrollCaptureResponse = null; + } + if (responseFuture.isCancelled()) { + return; } mLastScrollCaptureResponse = responseFuture.get(); if (!mLastScrollCaptureResponse.isConnected()) { @@ -707,8 +713,6 @@ public class ScreenshotController { // delay starting scroll capture to make sure the scrim is up before the app moves mScreenshotView.post(() -> runBatchScrollCapture(response)); }); - } catch (CancellationException e) { - // Ignore } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestScrollCapture failed", e); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 924351df3117..7f3758e208db 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -27,6 +27,7 @@ import static com.android.systemui.screenshot.LogConfig.logTag; import android.annotation.MainThread; import android.app.Service; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -42,9 +43,11 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.view.WindowManager; +import android.widget.Toast; import androidx.annotation.NonNull; @@ -62,9 +65,11 @@ public class TakeScreenshotService extends Service { private ScreenshotController mScreenshot; private final UserManager mUserManager; + private final DevicePolicyManager mDevicePolicyManager; private final UiEventLogger mUiEventLogger; private final ScreenshotNotificationsController mNotificationsController; private final Handler mHandler; + private final Context mContext; private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() { @Override @@ -91,16 +96,18 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager, - UiEventLogger uiEventLogger, - ScreenshotNotificationsController notificationsController) { + DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger, + ScreenshotNotificationsController notificationsController, Context context) { if (DEBUG_SERVICE) { Log.d(TAG, "new " + this); } mHandler = new Handler(Looper.getMainLooper(), this::handleMessage); mScreenshot = screenshotController; mUserManager = userManager; + mDevicePolicyManager = devicePolicyManager; mUiEventLogger = uiEventLogger; mNotificationsController = notificationsController; + mContext = context; } @Override @@ -182,6 +189,14 @@ 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(); + return true; + } ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 0a616c095551..270bdc785178 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -17,7 +17,6 @@ import android.util.MathUtils.lerp import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold -import com.android.systemui.util.getColorWithAlpha import java.util.function.Consumer /** @@ -368,7 +367,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } if (startColorAlpha > 0f) { - canvas.drawColor(getColorWithAlpha(revealGradientEndColor, startColorAlpha)) + canvas.drawColor(updateColorAlpha(revealGradientEndColor, startColorAlpha)) } with(shaderGradientMatrix) { @@ -384,7 +383,15 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, private fun setPaintColorFilter() { gradientPaint.colorFilter = PorterDuffColorFilter( - getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha), + updateColorAlpha(revealGradientEndColor, revealGradientEndColorAlpha), PorterDuff.Mode.MULTIPLY) } + + private fun updateColorAlpha(color: Int, alpha: Float): Int = + Color.argb( + (alpha * 255).toInt(), + Color.red(color), + Color.green(color), + Color.blue(color) + ) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index afce945a5bc5..734bc48093b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -39,7 +39,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; -import com.android.systemui.statusbar.notification.row.NotificationBackgroundView; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; @@ -411,7 +410,7 @@ public class NotificationShelf extends ActivatableNotificationView implements setBackgroundTop(backgroundTop); setFirstElementRoundness(firstElementRoundness); mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); - mShelfIcons.calculateIconTranslations(); + mShelfIcons.calculateIconXTranslations(); mShelfIcons.applyIconStates(); for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { View child = mHostLayoutController.getChildAt(i); @@ -636,7 +635,7 @@ public class NotificationShelf extends ActivatableNotificationView implements float viewEnd = viewStart + fullHeight; float fullTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f; - float shelfStart = getTranslationY(); + float shelfStart = getTranslationY() - mPaddingBetweenElements; if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) { // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) { 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 51af9559eda2..6f65131ba452 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 @@ -709,8 +709,8 @@ public class ShadeListBuilder implements Dumpable { new ArraySet<>(groupsWithChildrenLostToStability); // Any group which lost a child to filtering or promotion is exempt from having its summary // promoted when it has no attached children. - getGroupsWithChildrenLostToFiltering(groupsExemptFromSummaryPromotion); - getGroupsWithChildrenLostToPromotion(shadeList, groupsExemptFromSummaryPromotion); + addGroupsWithChildrenLostToFiltering(groupsExemptFromSummaryPromotion); + addGroupsWithChildrenLostToPromotion(shadeList, groupsExemptFromSummaryPromotion); // Iterate backwards, so that we can remove elements without affecting indices of // yet-to-be-accessed entries. @@ -865,7 +865,7 @@ public class ShadeListBuilder implements Dumpable { * * These groups will be exempt from appearing without any children. */ - private void getGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList, Set<String> out) { + private void addGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList, Set<String> out) { for (int i = 0; i < shadeList.size(); i++) { final ListEntry tle = shadeList.get(i); if (tle.getAttachState().getPromoter() != null) { @@ -882,13 +882,13 @@ public class ShadeListBuilder implements Dumpable { * * These groups will be exempt from appearing without any children. */ - private void getGroupsWithChildrenLostToFiltering(Set<String> out) { + private void addGroupsWithChildrenLostToFiltering(Set<String> out) { for (ListEntry tle : mAllEntries) { StatusBarNotification sbn = tle.getRepresentativeEntry().getSbn(); if (sbn.isGroup() && !sbn.getNotification().isGroupSummary() && tle.getAttachState().getExcludingFilter() != null) { - out.add(sbn.getGroup()); + out.add(sbn.getGroupKey()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 032e6784ae08..386e2d31380c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.annotation.MainThread import android.view.View +import com.android.systemui.util.kotlin.transform import com.android.systemui.util.traceSection /** @@ -40,6 +41,7 @@ class ShadeViewDiffer( ) { private val rootNode = ShadeNode(rootController) private val nodes = mutableMapOf(rootController to rootNode) + private val views = mutableMapOf<View, ShadeNode>() /** * Adds and removes views from the root (and its children) until their structure matches the @@ -64,25 +66,26 @@ class ShadeViewDiffer( * * For debugging purposes. */ - fun getViewLabel(view: View): String = - nodes.values.firstOrNull { node -> node.view === view }?.label ?: view.toString() - - private fun detachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) { - val views = nodes.values.asSequence().map { node -> node.view to node }.toMap() - fun detachRecursively(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) { - val parentSpec = specMap[parentNode.controller] - for (i in parentNode.getChildCount() - 1 downTo 0) { - val childView = parentNode.getChildAt(i) - views[childView]?.let { childNode -> - val childSpec = specMap[childNode.controller] - maybeDetachChild(parentNode, parentSpec, childNode, childSpec) - if (childNode.controller.getChildCount() > 0) { - detachRecursively(childNode, specMap) - } + fun getViewLabel(view: View): String = views[view]?.label ?: view.toString() + + private fun detachChildren( + parentNode: ShadeNode, + specMap: Map<NodeController, NodeSpec> + ) { + val parentSpec = specMap[parentNode.controller] + + for (i in parentNode.getChildCount() - 1 downTo 0) { + val childView = parentNode.getChildAt(i) + views[childView]?.let { childNode -> + val childSpec = specMap[childNode.controller] + + maybeDetachChild(parentNode, parentSpec, childNode, childSpec) + + if (childNode.controller.getChildCount() > 0) { + detachChildren(childNode, specMap) } } } - detachRecursively(parentNode, specMap) } private fun maybeDetachChild( @@ -91,13 +94,14 @@ class ShadeViewDiffer( childNode: ShadeNode, childSpec: NodeSpec? ) { - val newParentNode = childSpec?.parent?.let { getNode(it) } + val newParentNode = transform(childSpec?.parent) { getNode(it) } if (newParentNode != parentNode) { val childCompletelyRemoved = newParentNode == null if (childCompletelyRemoved) { nodes.remove(childNode.controller) + views.remove(childNode.controller.view) } logger.logDetachingChild( @@ -111,7 +115,10 @@ class ShadeViewDiffer( } } - private fun attachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) { + private fun attachChildren( + parentNode: ShadeNode, + specMap: Map<NodeController, NodeSpec> + ) { val parentSpec = checkNotNull(specMap[parentNode.controller]) for ((index, childSpec) in parentSpec.children.withIndex()) { @@ -153,6 +160,7 @@ class ShadeViewDiffer( if (node == null) { node = ShadeNode(spec.controller) nodes[node.controller] = node + views[node.view] = node } return node } @@ -186,9 +194,10 @@ class ShadeViewDiffer( private class DuplicateNodeException(message: String) : RuntimeException(message) -private class ShadeNode(val controller: NodeController) { - val view: View - get() = controller.view +private class ShadeNode( + val controller: NodeController +) { + val view = controller.view var parent: ShadeNode? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 2b11f693da1d..9ea36d540f4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -207,9 +207,7 @@ constructor( visibleIndex: Int ): Float { var height = stack.calculateGapHeight(previous, current, visibleIndex) - if (visibleIndex != 0) { - height += dividerHeight - } + height += dividerHeight return height } 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 22242b8fb4b4..2b79986662f1 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 @@ -314,7 +314,8 @@ public class StackScrollAlgorithm { if (ambientState.getShelf() != null) { final float shelfStart = ambientState.getStackEndHeight() - - ambientState.getShelf().getIntrinsicHeight(); + - ambientState.getShelf().getIntrinsicHeight() + - mPaddingBetweenElements; if (currentY >= shelfStart && !(view instanceof FooterView) && state.firstViewInShelf == null) { @@ -507,8 +508,9 @@ public class StackScrollAlgorithm { || bypassPulseNotExpanding ? ambientState.getInnerHeight() : (int) ambientState.getStackHeight(); - final int shelfStart = - stackBottom - ambientState.getShelf().getIntrinsicHeight(); + final int shelfStart = stackBottom + - ambientState.getShelf().getIntrinsicHeight() + - mPaddingBetweenElements; viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart); if (viewState.yTranslation >= shelfStart) { viewState.hidden = !view.isExpandAnimationRunning() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 83017c41629c..62b11c59923f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -2951,8 +2951,6 @@ public class CentralSurfaces extends CoreStartable implements public void showKeyguardImpl() { Trace.beginSection("CentralSurfaces#showKeyguard"); - // In case we're locking while a smartspace transition is in progress, reset it. - mKeyguardUnlockAnimationController.resetSmartspaceTransition(); if (mKeyguardStateController.isLaunchTransitionFadingAway()) { mNotificationPanelViewController.cancelAnimation(); onLaunchTransitionFadingEnded(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt index 289dfc889e75..178c17dd5694 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt @@ -18,9 +18,11 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager import android.view.View +import android.widget.TextView import androidx.constraintlayout.motion.widget.MotionLayout import com.android.settingslib.Utils import com.android.systemui.Dumpable +import com.android.systemui.FontSizeUtils import com.android.systemui.R import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView @@ -30,10 +32,12 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.qs.ChipVisibilityListener import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER +import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import javax.inject.Inject import javax.inject.Named @@ -43,6 +47,7 @@ class LargeScreenShadeHeaderController @Inject constructor( @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View, private val statusBarIconController: StatusBarIconController, private val privacyIconsController: HeaderPrivacyIconsController, + private val configurationController: ConfigurationController, qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, featureFlags: FeatureFlags, @Named(LARGE_SCREEN_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController, @@ -69,6 +74,9 @@ class LargeScreenShadeHeaderController @Inject constructor( private val iconContainer: StatusIconContainer private val carrierIconSlots: List<String> private val qsCarrierGroupController: QSCarrierGroupController + private val clock: TextView = header.findViewById(R.id.clock) + private val date: TextView = header.findViewById(R.id.date) + private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group) private var qsDisabled = false @@ -148,9 +156,9 @@ class LargeScreenShadeHeaderController @Inject constructor( .load(context, resources.getXml(R.xml.large_screen_shade_header)) privacyIconsController.chipVisibilityListener = chipVisibilityListener } - } - init { + bindConfigurationListener() + batteryMeterViewController.init() val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon) @@ -194,6 +202,18 @@ class LargeScreenShadeHeaderController @Inject constructor( } } + private fun bindConfigurationListener() { + val listener = object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + val qsStatusStyle = R.style.TextAppearance_QS_Status + FontSizeUtils.updateFontSizeFromStyle(clock, qsStatusStyle) + FontSizeUtils.updateFontSizeFromStyle(date, qsStatusStyle) + qsCarrierGroup.updateTextAppearance(qsStatusStyle) + } + } + configurationController.addCallback(listener) + } + private fun onShadeExpandedChanged() { if (shadeExpanded) { privacyIconsController.startListening() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 034b751d1e61..2dc3261eb886 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -27,11 +27,13 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Icon; import android.util.AttributeSet; +import android.util.MathUtils; import android.util.Property; import android.view.ContextThemeWrapper; import android.view.View; import android.view.animation.Interpolator; +import androidx.annotation.VisibleForTesting; import androidx.collection.ArrayMap; import com.android.internal.statusbar.StatusBarIcon; @@ -136,6 +138,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { }.setDuration(CONTENT_FADE_DURATION); private static final int MAX_ICONS_ON_AOD = 3; + + /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */ public static final int MAX_ICONS_ON_LOCKSCREEN = 3; public static final int MAX_STATIC_ICONS = 4; private static final int MAX_DOTS = 1; @@ -145,7 +149,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mDotPadding; private int mStaticDotRadius; private int mStaticDotDiameter; - private int mOverflowWidth; private int mActualLayoutWidth = NO_VALUE; private float mActualPaddingEnd = NO_VALUE; private float mActualPaddingStart = NO_VALUE; @@ -219,10 +222,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { paint.setColor(Color.RED); canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint); - - paint.setColor(Color.YELLOW); - float overflow = getMaxOverflowStart(); - canvas.drawLine(overflow, 0, overflow, height, paint); } } @@ -255,14 +254,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } } - private void setIconSize(int size) { + @VisibleForTesting + public void setIconSize(int size) { mIconSize = size; - mOverflowWidth = mIconSize + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding); } private void updateState() { resetViewStates(); - calculateIconTranslations(); + calculateIconXTranslations(); applyIconStates(); } @@ -390,12 +389,11 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { * @return Width of shelf for the given number of icons */ public float calculateWidthFor(float numIcons) { - if (getChildCount() == 0) { + if (numIcons == 0) { return 0f; } - final float contentWidth = numIcons <= MAX_ICONS_ON_LOCKSCREEN + 1 - ? numIcons * mIconSize - : MAX_ICONS_ON_LOCKSCREEN * mIconSize + (float) mOverflowWidth; + final float contentWidth = + mIconSize * MathUtils.min(numIcons, MAX_ICONS_ON_LOCKSCREEN + 1); return getActualPaddingStart() + contentWidth + getActualPaddingEnd(); @@ -406,14 +404,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { * are inserted into the notification container. * If this is not a whole number, the fraction means by how much the icon is appearing. */ - public void calculateIconTranslations() { + public void calculateIconXTranslations() { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD : mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); - float overflowStart = getMaxOverflowStart(); mVisualOverflowStart = 0; mFirstVisibleIconState = null; for (int i = 0; i < childCount; i++) { @@ -438,12 +435,12 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { ? StatusBarIconView.STATE_HIDDEN : StatusBarIconView.STATE_ICON; - boolean isOverflowing = - (translationX > (isLastChild ? layoutEnd - mIconSize - : overflowStart - mIconSize)); + final float overflowDotX = layoutEnd - mIconSize; + boolean isOverflowing = translationX > overflowDotX; + if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) { firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i; - mVisualOverflowStart = layoutEnd - mOverflowWidth; + mVisualOverflowStart = layoutEnd - mIconSize; if (forceOverflow || mIsStaticLayout) { mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); } @@ -477,7 +474,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mLastVisibleIconState = mIconStates.get(lastChild); mFirstVisibleIconState = mIconStates.get(getChildAt(0)); } - if (isLayoutRtl()) { for (int i = 0; i < childCount; i++) { View view = getChildAt(i); @@ -568,7 +564,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } private float getMaxOverflowStart() { - return getLayoutEnd() - mOverflowWidth; + return getLayoutEnd() - mIconSize; } public void setChangingViewPositions(boolean changingViewPositions) { @@ -635,7 +631,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { return 0; } - int collapsedPadding = mOverflowWidth; + int collapsedPadding = mIconSize; if (collapsedPadding + getFinalTranslationX() > getWidth()) { collapsedPadding = getWidth() - getFinalTranslationX(); 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 0b46f07f8e8b..807122b62e9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -885,7 +885,10 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onUnlockAnimationStarted( - boolean playingCannedAnimation, boolean isWakeAndUnlock) { + boolean playingCannedAnimation, + boolean isWakeAndUnlock, + long unlockAnimationStartDelay, + long unlockAnimationDuration) { // Disable blurs while we're unlocking so that panel expansion does not // cause blurring. This will eventually be re-enabled by the panel view on // ACTION_UP, since the user's finger might still be down after a swipe to @@ -902,7 +905,22 @@ public class NotificationPanelViewController extends PanelViewController { onTrackingStopped(false); instantCollapse(); } else { - fling(0f, false, 1f, false); + mView.animate() + .alpha(0f) + .setStartDelay(0) + // Translate up by 4%. + .translationY(mView.getHeight() * -0.04f) + // This start delay is to give us time to animate out before + // the launcher icons animation starts, so use that as our + // duration. + .setDuration(unlockAnimationStartDelay) + .setInterpolator(EMPHASIZED_DECELERATE) + .withEndAction(() -> { + instantCollapse(); + mView.setAlpha(1f); + mView.setTranslationY(0f); + }) + .start(); } } } @@ -3178,12 +3196,6 @@ public class NotificationPanelViewController extends PanelViewController { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); super.onTrackingStarted(); mScrimController.onTrackingStarted(); - // normally we want to set mQsExpandImmediate for every split shade case (at least when - // expanding), but keyguard tracking logic is different - this callback is called when - // unlocking with swipe up but not when swiping down to reveal shade - if (mShouldUseSplitNotificationShade && !mKeyguardShowing) { - mQsExpandImmediate = true; - } if (mQsFullyExpanded) { mQsExpandImmediate = true; setShowShelfOnly(true); @@ -4930,6 +4942,12 @@ public class NotificationPanelViewController extends PanelViewController { mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } if (state == STATE_OPENING) { + // we need to ignore it on keyguard as this is a false alarm - transition from unlocked + // to locked will trigger this event and we're not actually in the process of opening + // the shade, lockscreen is just always expanded + if (mShouldUseSplitNotificationShade && !isOnKeyguard()) { + mQsExpandImmediate = true; + } mCentralSurfaces.makeExpandedVisible(false); } if (state == STATE_CLOSED) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 2a9048a6eb73..169347a5ac1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -78,6 +78,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> private final UiEventLogger mUiEventLogger; @VisibleForTesting UserAvatarView mUserAvatarView; + private View mUserAvatarViewWithBackground; UserSwitcherController.UserRecord mCurrentUser; private boolean mIsKeyguardShowing; @@ -167,6 +168,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> super.onInit(); if (DEBUG) Log.d(TAG, "onInit"); mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); + mUserAvatarViewWithBackground = mView.findViewById( + R.id.kg_multi_user_avatar_with_background); mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { @@ -186,7 +189,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUiEventLogger.log( LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); - mUserSwitchDialogController.showDialog(mView); + mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground); }); mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7920d388c670..a50d3d607aec 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -474,7 +474,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mThemeStyle = fetchThemeStyleFromSetting(); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); - if (colorSchemeIsApplied()) { + if (colorSchemeIsApplied() && !forceReload) { Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme); return; } diff --git a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt deleted file mode 100644 index 27a53bf2ceda..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import android.content.res.TypedArray -import android.graphics.Color -import android.view.ContextThemeWrapper - -/** Returns an ARGB color version of [color] at the given [alpha]. */ -fun getColorWithAlpha(color: Int, alpha: Float): Int = - Color.argb( - (alpha * 255).toInt(), - Color.red(color), - Color.green(color), - Color.blue(color) - ) - - -/** - * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists, - * otherwise, returns the color from the private attribute {@param privAttrId}. - */ -fun getPrivateAttrColorIfUnset( - ctw: ContextThemeWrapper, attrArray: TypedArray, - attrIndex: Int, defColor: Int, privAttrId: Int -): Int { - // If the index is specified, use that value - var a = attrArray - if (a.hasValue(attrIndex)) { - return a.getColor(attrIndex, defColor) - } - - // Otherwise fallback to the value of the private attribute - val customAttrs = intArrayOf(privAttrId) - a = ctw.obtainStyledAttributes(customAttrs) - val color = a.getColor(0, defColor) - a.recycle() - return color -} diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 5b5dca30620a..8e5e1d2e1b87 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -105,6 +105,25 @@ public class Utils { } /** + * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists, + * otherwise, returns the color from the private attribute {@param privAttrId}. + */ + public static int getPrivateAttrColorIfUnset(ContextThemeWrapper ctw, TypedArray a, + int attrIndex, int defColor, int privAttrId) { + // If the index is specified, use that value + if (a.hasValue(attrIndex)) { + return a.getColor(attrIndex, defColor); + } + + // Otherwise fallback to the value of the private attribute + int[] customAttrs = { privAttrId }; + a = ctw.obtainStyledAttributes(customAttrs); + int color = a.getColor(0, defColor); + a.recycle(); + return color; + } + + /** * Gets the {@link R.dimen#status_bar_header_height_keyguard}. */ public static int getStatusBarHeaderHeightKeyguard(Context context) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java index 4beec574cd2a..01365b43b4b8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java @@ -70,7 +70,7 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { private Display mSecondaryDisplay; // This display is in a different group from the default and secondary displays. - private Display mDifferentGroupDisplay; + private Display mAlwaysUnlockedDisplay; @Before public void setUp() { @@ -86,12 +86,12 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { Display.DEFAULT_DISPLAY + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); - DisplayInfo differentGroupInfo = new DisplayInfo(); - differentGroupInfo.displayId = Display.DEFAULT_DISPLAY + 2; - differentGroupInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; - mDifferentGroupDisplay = new Display(DisplayManagerGlobal.getInstance(), + DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo(); + alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2; + alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED; + mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, - differentGroupInfo, DEFAULT_DISPLAY_ADJUSTMENTS); + alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); } @Test @@ -110,18 +110,18 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { } @Test - public void testShow_includeNonDefaultGroupDisplay() { + public void testShow_includeAlwaysUnlockedDisplay() { when(mDisplayManager.getDisplays()).thenReturn( - new Display[]{mDefaultDisplay, mDifferentGroupDisplay}); + new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay}); mManager.show(); verify(mManager, never()).createPresentation(any()); } @Test - public void testShow_includeSecondaryAndNonDefaultGroupDisplays() { + public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() { when(mDisplayManager.getDisplays()).thenReturn( - new Display[]{mDefaultDisplay, mSecondaryDisplay, mDifferentGroupDisplay}); + new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay}); mManager.show(); verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 1753157c631d..650a5d0a8712 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -22,7 +22,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -55,8 +54,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { @Mock DozeParameters mDozeParameters; @Mock - KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock ScreenOffAnimationController mScreenOffAnimationController; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; @@ -75,7 +72,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mConfigurationController, mDozeParameters, - mKeyguardUnlockAnimationController, mScreenOffAnimationController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt index e62b4e63e3d5..55f0591b6ce0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt @@ -142,6 +142,25 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { assertThat(cutoutBaseView.protectionRect).isEqualTo(pathBounds) } + @Test + fun testCutoutProtection_withDisplayRatio() { + setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */) + whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(0.5f) + val bounds = Rect(0, 0, 10, 10) + val path = Path() + val pathBounds = RectF(bounds) + path.addRect(pathBounds, Path.Direction.CCW) + + context.mainExecutor.execute { + cutoutBaseView.setProtection(path, bounds) + cutoutBaseView.enableShowProtection(true) + } + waitForIdleSync() + + assertThat(cutoutBaseView.protectionPath.isRect(pathBounds)).isTrue() + assertThat(cutoutBaseView.protectionRect).isEqualTo(RectF(0f, 0f, 5f, 5f)) + } + private fun setupDisplayCutoutBaseView(fillCutout: Boolean, hasCutout: Boolean) { mContext.orCreateTestableResources.addOverride( R.array.config_displayUniqueIdArray, arrayOf<String>()) @@ -151,6 +170,7 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { cutoutBaseView = spy(DisplayCutoutBaseView(mContext)) whenever(cutoutBaseView.display).thenReturn(mockDisplay) whenever(cutoutBaseView.rootView).thenReturn(mockRootView) + whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f) whenever(mockDisplay.getDisplayInfo(eq(cutoutBaseView.displayInfo)) ).then { val info = it.getArgument<DisplayInfo>(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt index 23129d247ad5..6a9bb3e343be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt @@ -6,8 +6,10 @@ import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.widget.LinearLayout +import android.widget.RelativeLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.util.children import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNotNull @@ -28,18 +30,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { private val TEST_INTERPOLATOR = Interpolators.LINEAR } - private val childParams = LinearLayout.LayoutParams( - 0 /* width */, - LinearLayout.LayoutParams.MATCH_PARENT - ) - private lateinit var rootView: LinearLayout + private lateinit var rootView: ViewGroup @Before fun setUp() { rootView = LinearLayout(mContext) - rootView.orientation = LinearLayout.HORIZONTAL - rootView.weightSum = 1f - childParams.weight = 0.5f } @After @@ -93,6 +88,19 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator assertEquals(animator.interpolator, TEST_INTERPOLATOR) assertEquals(animator.duration, TEST_DURATION) + + // animateRemoval() + setUpRootWithChildren() + val child = rootView.getChildAt(0) + success = ViewHierarchyAnimator.animateRemoval( + child, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION + ) + + assertTrue(success) + assertNotNull(child.getTag(R.id.tag_animator)) + animator = child.getTag(R.id.tag_animator) as ObjectAnimator + assertEquals(animator.interpolator, TEST_INTERPOLATOR) + assertEquals(animator.duration, TEST_DURATION) } @Test @@ -170,17 +178,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { @Test fun animatesRootAndChildren() { - val firstChild = View(mContext) - firstChild.layoutParams = childParams - rootView.addView(firstChild) - val secondChild = View(mContext) - secondChild.layoutParams = childParams - rootView.addView(secondChild) - rootView.measure( - View.MeasureSpec.makeMeasureSpec(150, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) - ) - rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */) + setUpRootWithChildren() val success = ViewHierarchyAnimator.animate(rootView) // Change all bounds. @@ -192,20 +190,20 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { assertTrue(success) assertNotNull(rootView.getTag(R.id.tag_animator)) - assertNotNull(firstChild.getTag(R.id.tag_animator)) - assertNotNull(secondChild.getTag(R.id.tag_animator)) + assertNotNull(rootView.getChildAt(0).getTag(R.id.tag_animator)) + assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator)) // The initial values should be those of the previous layout. - checkBounds(rootView, l = 0, t = 0, r = 150, b = 100) - checkBounds(firstChild, l = 0, t = 0, r = 75, b = 100) - checkBounds(secondChild, l = 75, t = 0, r = 150, b = 100) + checkBounds(rootView, l = 0, t = 0, r = 200, b = 100) + checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 100, b = 100) + checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100) endAnimation(rootView) assertNull(rootView.getTag(R.id.tag_animator)) - assertNull(firstChild.getTag(R.id.tag_animator)) - assertNull(secondChild.getTag(R.id.tag_animator)) + assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator)) + assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator)) // The end values should be those of the latest layout. checkBounds(rootView, l = 10, t = 20, r = 200, b = 120) - checkBounds(firstChild, l = 0, t = 0, r = 95, b = 100) - checkBounds(secondChild, l = 95, t = 0, r = 190, b = 100) + checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 95, b = 100) + checkBounds(rootView.getChildAt(1), l = 95, t = 0, r = 190, b = 100) } @Test @@ -522,6 +520,251 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { endAnimation(rootView) } + fun animatesViewRemovalFromStartToEnd() { + setUpRootWithChildren() + + val child = rootView.getChildAt(0) + val success = ViewHierarchyAnimator.animateRemoval( + child, + destination = ViewHierarchyAnimator.Hotspot.LEFT, + interpolator = Interpolators.LINEAR + ) + + assertTrue(success) + assertNotNull(child.getTag(R.id.tag_animator)) + checkBounds(child, l = 0, t = 0, r = 100, b = 100) + advanceAnimation(child, 0.5f) + checkBounds(child, l = 0, t = 0, r = 50, b = 100) + advanceAnimation(child, 1.0f) + checkBounds(child, l = 0, t = 0, r = 0, b = 100) + endAnimation(rootView) + endAnimation(child) + assertEquals(1, rootView.childCount) + assertFalse(child in rootView.children) + } + + @Test + fun animatesViewRemovalRespectingDestination() { + // CENTER + setUpRootWithChildren() + var removedChild = rootView.getChildAt(0) + var remainingChild = rootView.getChildAt(1) + var success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.CENTER + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 50, t = 50, r = 50, b = 50) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // LEFT + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.LEFT + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 0, t = 0, r = 0, b = 100) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // TOP_LEFT + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 0, t = 0, r = 0, b = 0) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // TOP + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 0, t = 0, r = 100, b = 0) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // TOP_RIGHT + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 100, t = 0, r = 100, b = 0) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // RIGHT + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.RIGHT + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 100, t = 0, r = 100, b = 100) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // BOTTOM_RIGHT + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 100, t = 100, r = 100, b = 100) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // BOTTOM + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 0, t = 100, r = 100, b = 100) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + + // BOTTOM_LEFT + setUpRootWithChildren() + removedChild = rootView.getChildAt(0) + remainingChild = rootView.getChildAt(1) + success = ViewHierarchyAnimator.animateRemoval( + removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(removedChild.getTag(R.id.tag_animator)) + advanceAnimation(removedChild, 1.0f) + checkBounds(removedChild, l = 0, t = 100, r = 0, b = 100) + endAnimation(rootView) + endAnimation(removedChild) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + } + + @Test + fun animatesChildrenDuringViewRemoval() { + setUpRootWithChildren() + + val child = rootView.getChildAt(0) as ViewGroup + val firstGrandChild = child.getChildAt(0) + val secondGrandChild = child.getChildAt(1) + val success = ViewHierarchyAnimator.animateRemoval( + child, interpolator = Interpolators.LINEAR + ) + + assertTrue(success) + assertNotNull(child.getTag(R.id.tag_animator)) + assertNotNull(firstGrandChild.getTag(R.id.tag_animator)) + assertNotNull(secondGrandChild.getTag(R.id.tag_animator)) + checkBounds(child, l = 0, t = 0, r = 100, b = 100) + checkBounds(firstGrandChild, l = 0, t = 0, r = 40, b = 40) + checkBounds(secondGrandChild, l = 60, t = 60, r = 100, b = 100) + + advanceAnimation(child, 0.5f) + checkBounds(child, l = 25, t = 25, r = 75, b = 75) + checkBounds(firstGrandChild, l = -10, t = -10, r = 30, b = 30) + checkBounds(secondGrandChild, l = 20, t = 20, r = 60, b = 60) + + advanceAnimation(child, 1.0f) + checkBounds(child, l = 50, t = 50, r = 50, b = 50) + checkBounds(firstGrandChild, l = -20, t = -20, r = 20, b = 20) + checkBounds(secondGrandChild, l = -20, t = -20, r = 20, b = 20) + + endAnimation(rootView) + endAnimation(child) + } + + @Test + fun animatesSiblingsDuringViewRemoval() { + setUpRootWithChildren() + + val removedChild = rootView.getChildAt(0) + val remainingChild = rootView.getChildAt(1) + val success = ViewHierarchyAnimator.animateRemoval( + removedChild, interpolator = Interpolators.LINEAR + ) + // Ensure that the layout happens before the checks. + forceLayout() + + assertTrue(success) + assertNotNull(remainingChild.getTag(R.id.tag_animator)) + checkBounds(remainingChild, l = 100, t = 0, r = 200, b = 100) + advanceAnimation(rootView, 0.5f) + checkBounds(remainingChild, l = 50, t = 0, r = 150, b = 100) + advanceAnimation(rootView, 1.0f) + checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) + endAnimation(rootView) + endAnimation(removedChild) + assertNull(remainingChild.getTag(R.id.tag_animator)) + } + @Test fun cleansUpListenersCorrectly() { val firstChild = View(mContext) @@ -700,6 +943,49 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) } + private fun setUpRootWithChildren() { + rootView = LinearLayout(mContext) + (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL + (rootView as LinearLayout).weightSum = 1f + + val firstChild = RelativeLayout(mContext) + rootView.addView(firstChild) + val firstGrandChild = View(mContext) + firstChild.addView(firstGrandChild) + val secondGrandChild = View(mContext) + firstChild.addView(secondGrandChild) + val secondChild = View(mContext) + rootView.addView(secondChild) + + val childParams = LinearLayout.LayoutParams( + 0 /* width */, + LinearLayout.LayoutParams.MATCH_PARENT + ) + childParams.weight = 0.5f + firstChild.layoutParams = childParams + secondChild.layoutParams = childParams + firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */) + (firstGrandChild.layoutParams as RelativeLayout.LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_START) + (firstGrandChild.layoutParams as RelativeLayout.LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_TOP) + secondGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */) + (secondGrandChild.layoutParams as RelativeLayout.LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_END) + (secondGrandChild.layoutParams as RelativeLayout.LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + + forceLayout() + } + + private fun forceLayout() { + rootView.measure( + View.MeasureSpec.makeMeasureSpec(200 /* width */, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST) + ) + rootView.layout(0 /* l */, 0 /* t */, 200 /* r */, 100 /* b */) + } + private fun checkBounds(v: View, l: Int, t: Int, r: Int, b: Int) { assertEquals(l, v.left) assertEquals(t, v.top) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index fb1a968acceb..2d8c4d5dceb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -17,6 +17,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -125,4 +126,69 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( false /* cancelled */) } + + /** + * If we requested that the surface behind be made visible, and we're not flinging away the + * keyguard, it means that we're swiping to unlock and want the surface visible so it can follow + * the user's touch event as they swipe to unlock. + * + * In this case, we should verify that the surface was made visible via the alpha fade in + * animator, and verify that we did not start the canned animation to animate the surface in + * (since it's supposed to be following the touch events). + */ + @Test + fun fadeInSurfaceBehind_ifRequestedShowSurface_butNotFlinging() { + `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false) + + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + remoteAnimationTarget, + 0 /* startTime */, + true /* requestedShowSurfaceBehindKeyguard */ + ) + + assertTrue(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) + assertFalse(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) + } + + /** + * We requested the surface behind to be made visible, but we're now flinging to dismiss the + * keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and + * lifted their finger while we were requesting the surface be made visible. + * + * In this case, we should verify that we are playing the canned unlock animation and not + * simply fading in the surface. + */ + @Test + fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() { + `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true) + + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + remoteAnimationTarget, + 0 /* startTime */, + true /* requestedShowSurfaceBehindKeyguard */ + ) + + assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) + assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) + } + + /** + * We never requested the surface behind to be made visible, which means no swiping to unlock + * ever happened and we're just playing the simple canned animation (happens via UDFPS unlock, + * long press on the lock icon, etc). + * + * In this case, we should verify that we are playing the canned unlock animation and not + * simply fading in the surface. + */ + @Test + fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() { + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + remoteAnimationTarget, + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) + assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt index 65d501442d87..8f967ab5294f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.media import org.mockito.Mockito.`when` as whenever import android.animation.ValueAnimator import android.graphics.Color +import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.monet.ColorScheme import junit.framework.Assert.assertEquals @@ -46,35 +46,28 @@ class ColorSchemeTransitionTest : SysuiTestCase() { private interface ExtractCB : (ColorScheme) -> Int private interface ApplyCB : (Int) -> Unit - private lateinit var colorTransition: AnimatingColorTransition + private lateinit var colorTransition: ColorTransition private lateinit var colorSchemeTransition: ColorSchemeTransition - @Mock private lateinit var mockAnimatingTransition: AnimatingColorTransition - @Mock private lateinit var mockGenericTransition: GenericColorTransition + @Mock private lateinit var mockTransition: ColorTransition @Mock private lateinit var valueAnimator: ValueAnimator @Mock private lateinit var colorScheme: ColorScheme @Mock private lateinit var extractColor: ExtractCB @Mock private lateinit var applyColor: ApplyCB - private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory - private lateinit var genericColorTransitionFactory: GenericColorTransitionFactory + private lateinit var transitionFactory: ColorTransitionFactory @Mock private lateinit var mediaViewHolder: MediaViewHolder @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @Before fun setUp() { - animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition } - genericColorTransitionFactory = { _ -> mockGenericTransition } + transitionFactory = { default, extractColor, applyColor -> mockTransition } whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) - colorSchemeTransition = ColorSchemeTransition( - context, mediaViewHolder, animatingColorTransitionFactory, genericColorTransitionFactory - ) + colorSchemeTransition = ColorSchemeTransition(context, mediaViewHolder, transitionFactory) - colorTransition = object : AnimatingColorTransition( - DEFAULT_COLOR, extractColor, applyColor - ) { + colorTransition = object : ColorTransition(DEFAULT_COLOR, extractColor, applyColor) { override fun buildAnimator(): ValueAnimator { return valueAnimator } @@ -149,7 +142,6 @@ class ColorSchemeTransitionTest : SysuiTestCase() { @Test fun testColorSchemeTransition_update() { colorSchemeTransition.updateColorScheme(colorScheme) - verify(mockAnimatingTransition, times(6)).updateColorScheme(colorScheme) - verify(mockGenericTransition).updateColorScheme(colorScheme) + verify(mockTransition, times(6)).updateColorScheme(colorScheme) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 51088b1d929b..863484b62a00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -62,6 +62,16 @@ public class ColorSchemeTest extends SysuiTestCase { Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a)); } + @Test + public void testStyleApplied() { + WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a), + null, null); + // Expressive applies hue rotations to the theme color. The input theme color has hue + // 117, ensuring the hue changed significantly is a strong signal styles are being applied. + ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE); + Assert.assertEquals(Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 357.46, 0.1); + } + @Test public void testFiltersInvalidColors() { @@ -123,7 +133,7 @@ public class ColorSchemeTest extends SysuiTestCase { Style.VIBRANT /* style */); int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); Cam cam = Cam.fromInt(neutralMid); - Assert.assertTrue(cam.getChroma() <= 8.0); + Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 10.0); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt index c714fa0e5b27..1d687b141d1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt @@ -35,6 +35,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import android.content.Intent +import android.text.TextUtils @SmallTest @RunWith(AndroidTestingRunner::class) @@ -373,4 +374,31 @@ class PrivacyDialogTest : SysuiTestCase() { ) ) } + + @Test + fun testDialogHasTitle() { + // Dialog must have a non-empty title for a11y purposes. + + val list = listOf( + PrivacyDialog.PrivacyElement( + PrivacyType.TYPE_MICROPHONE, + TEST_PACKAGE_NAME, + TEST_USER_ID, + "App", + null, + null, + null, + 0L, + false, + false, + false, + TEST_PERM_GROUP, + null + ) + ) + dialog = PrivacyDialog(context, list, starter) + dialog.show() + + assertThat(TextUtils.isEmpty(dialog.window?.attributes?.title)).isFalse() + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 633a9c3a03d8..4a8cb0b76dc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -138,6 +138,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { private DialogLaunchAnimator mDialogLaunchAnimator; @Mock private View mDialogLaunchView; + @Mock + private WifiStateWorker mWifiStateWorker; private TestableResources mTestableResources; private InternetDialogController mInternetDialogController; @@ -166,6 +168,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { when(mSystemUIToast.getView()).thenReturn(mToastView); when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS); when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mInternetDialogController = new InternetDialogController(mContext, mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController, @@ -173,7 +176,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher, mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController, mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker, - mLocationController, mDialogLaunchAnimator); + mLocationController, mDialogLaunchAnimator, mWifiStateWorker); mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, mInternetDialogController.mOnSubscriptionsChangedListener); mInternetDialogController.onStart(mInternetDialogCallback, true); @@ -239,7 +242,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withApmOnAndWifiOff_returnWifiIsOff() { fakeAirplaneModeEnabled(true); - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(false); assertThat(mInternetDialogController.getSubtitleText(false)) .isEqualTo(getResourcesString("wifi_is_off")); @@ -254,7 +257,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withWifiOff_returnWifiIsOff() { fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(false); assertThat(mInternetDialogController.getSubtitleText(false)) .isEqualTo(getResourcesString("wifi_is_off")); @@ -269,7 +272,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withNoWifiEntry_returnSearchWifi() { fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); assertThat(mInternetDialogController.getSubtitleText(true)) @@ -286,7 +289,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { public void getSubtitleText_withWifiEntry_returnTapToConnect() { // The preconditions WiFi Entries is already in setUp() fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); assertThat(mInternetDialogController.getSubtitleText(false)) .isEqualTo(getResourcesString("tap_a_network_to_connect")); @@ -301,7 +304,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() { fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(false); assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false), @@ -311,7 +314,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withNoService_returnNoNetworksAvailable() { fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); @@ -325,7 +328,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() { fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState(); @@ -346,7 +349,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() { fakeAirplaneModeEnabled(false); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 616f89455c74..d09a5a11040f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -14,7 +14,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.net.wifi.WifiManager; import android.os.Handler; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; @@ -64,8 +63,6 @@ public class InternetDialogTest extends SysuiTestCase { @Mock private TelephonyManager mTelephonyManager; @Mock - private WifiManager mWifiManager; - @Mock private WifiEntry mInternetWifiEntry; @Mock private List<WifiEntry> mWifiEntries; @@ -97,7 +94,6 @@ public class InternetDialogTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); - when(mWifiManager.isWifiEnabled()).thenReturn(true); when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE); when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true); @@ -107,7 +103,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE); when(mInternetDialogController.getMobileNetworkSummary()) .thenReturn(MOBILE_NETWORK_SUMMARY); - when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager); + when(mInternetDialogController.isWifiEnabled()).thenReturn(true); mMockitoSession = ExtendedMockito.mockitoSession() .spyStatic(WifiEnterpriseRestrictionUtils.class) @@ -232,7 +228,7 @@ public class InternetDialogTest extends SysuiTestCase { // Carrier network should be gone if airplane mode ON and Wi-Fi is off. when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mInternetDialogController.isWifiEnabled()).thenReturn(false); mInternetDialog.updateDialog(true); @@ -241,7 +237,7 @@ public class InternetDialogTest extends SysuiTestCase { // Carrier network should be visible if airplane mode ON and Wi-Fi is ON. when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mInternetDialogController.isWifiEnabled()).thenReturn(true); mInternetDialog.updateDialog(true); @@ -468,7 +464,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() { - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mInternetDialogController.isWifiEnabled()).thenReturn(false); when(mInternetDialogController.isWifiScanEnabled()).thenReturn(false); mInternetDialog.updateDialog(false); @@ -478,7 +474,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOffAndWifiScanOnAndDeviceLocked_hideWifiScanNotify() { - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mInternetDialogController.isWifiEnabled()).thenReturn(false); when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true); when(mInternetDialogController.isDeviceLocked()).thenReturn(true); @@ -489,7 +485,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOffAndWifiScanOnAndDeviceUnlocked_showWifiScanNotify() { - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mInternetDialogController.isWifiEnabled()).thenReturn(false); when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true); when(mInternetDialogController.isDeviceLocked()).thenReturn(false); @@ -502,6 +498,26 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_wifiIsDisabled_uncheckWifiSwitch() { + when(mInternetDialogController.isWifiEnabled()).thenReturn(false); + mWifiToggleSwitch.setChecked(true); + + mInternetDialog.updateDialog(false); + + assertThat(mWifiToggleSwitch.isChecked()).isFalse(); + } + + @Test + public void updateDialog_wifiIsEnabled_checkWifiSwitch() { + when(mInternetDialogController.isWifiEnabled()).thenReturn(true); + mWifiToggleSwitch.setChecked(false); + + mInternetDialog.updateDialog(false); + + assertThat(mWifiToggleSwitch.isChecked()).isTrue(); + } + + @Test public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() { mSeeAll.performClick(); @@ -512,7 +528,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void showProgressBar_wifiDisabled_hideProgressBar() { Mockito.reset(mHandler); - when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mInternetDialogController.isWifiEnabled()).thenReturn(false); mInternetDialog.showProgressBar(); @@ -534,7 +550,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() { Mockito.reset(mHandler); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mInternetDialogController.isWifiEnabled()).thenReturn(true); mInternetDialog.showProgressBar(); @@ -553,7 +569,7 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() { Mockito.reset(mHandler); - when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mInternetDialogController.isWifiEnabled()).thenReturn(true); mInternetDialog.mConnectedWifiEntry = null; mInternetDialog.mWifiEntriesCount = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java new file mode 100644 index 000000000000..5d7ba7bc673d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java @@ -0,0 +1,205 @@ +/* + * 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.dialog; + +import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE; +import static android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.net.wifi.WifiManager; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class WifiStateWorkerTest extends SysuiTestCase { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private WifiManager mWifiManager; + @Mock + private Intent mIntent; + + private WifiStateWorker mWifiStateWorker; + private FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); + + @Before + public void setup() { + when(mWifiManager.setWifiEnabled(anyBoolean())).thenReturn(true); + when(mWifiManager.getWifiState()).thenReturn(WIFI_STATE_ENABLED); + when(mIntent.getAction()).thenReturn(WIFI_STATE_CHANGED_ACTION); + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_ENABLED); + + mWifiStateWorker = new WifiStateWorker(mBroadcastDispatcher, mBackgroundExecutor, + mWifiManager); + mBackgroundExecutor.runAllReady(); + } + + @Test + public void constructor_shouldGetWifiState() { + verify(mWifiManager).getWifiState(); + } + + @Test + public void setWifiEnabled_wifiManagerIsNull_shouldNotSetWifiEnabled() { + mWifiStateWorker = new WifiStateWorker(mBroadcastDispatcher, mBackgroundExecutor, + null /* wifiManager */); + + mWifiStateWorker.setWifiEnabled(true); + mBackgroundExecutor.runAllReady(); + + verify(mWifiManager, never()).setWifiEnabled(anyBoolean()); + } + + @Test + public void setWifiEnabled_enabledIsTrue_shouldSetWifiEnabled() { + mWifiStateWorker.setWifiEnabled(true); + mBackgroundExecutor.runAllReady(); + + verify(mWifiManager).setWifiEnabled(true); + } + + @Test + public void setWifiEnabled_enabledIsFalse_shouldSetWifiDisabled() { + mWifiStateWorker.setWifiEnabled(false); + mBackgroundExecutor.runAllReady(); + + verify(mWifiManager).setWifiEnabled(false); + } + + @Test + public void getWifiState_receiveWifiStateDisabling_getWifiStateDisabling() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_DISABLING); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_DISABLING); + } + + @Test + public void getWifiState_receiveWifiStateDisabled_getWifiStateDisabled() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_DISABLED); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_DISABLED); + } + + @Test + public void getWifiState_receiveWifiStateEnabling_getWifiStateEnabling() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_ENABLING); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_ENABLING); + } + + @Test + public void getWifiState_receiveWifiStateEnabled_getWifiStateEnabled() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_ENABLED); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_ENABLED); + } + + @Test + public void getWifiState_receiveWifiStateUnknown_ignoreTheIntent() { + // Update the Wi-Fi state to WIFI_STATE_DISABLED + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_DISABLED); + mWifiStateWorker.onReceive(mContext, mIntent); + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_DISABLED); + + // Receiver WIFI_STATE_UNKNOWN + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_UNKNOWN); + mWifiStateWorker.onReceive(mContext, mIntent); + + // Ignore the intent and keep the Wi-Fi state to WIFI_STATE_DISABLED + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_DISABLED); + + // Update the Wi-Fi state to WIFI_STATE_ENABLED + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_ENABLED); + mWifiStateWorker.onReceive(mContext, mIntent); + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_ENABLED); + + // Receiver WIFI_STATE_UNKNOWN change + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_UNKNOWN); + mWifiStateWorker.onReceive(mContext, mIntent); + + // Ignore the intent and keep the Wi-Fi state to WIFI_STATE_ENABLED + assertThat(mWifiStateWorker.getWifiState()).isEqualTo(WIFI_STATE_ENABLED); + } + + @Test + public void isWifiEnabled_receiveWifiStateDisabling_returnFalse() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_DISABLING); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.isWifiEnabled()).isFalse(); + } + + @Test + public void isWifiEnabled_receiveWifiStateDisabled_returnFalse() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_DISABLED); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.isWifiEnabled()).isFalse(); + } + + @Test + public void isWifiEnabled_receiveWifiStateEnabling_returnTrue() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_ENABLING); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.isWifiEnabled()).isTrue(); + } + + @Test + public void isWifiEnabled_receiveWifiStateEnabled_returnTrue() { + when(mIntent.getIntExtra(eq(EXTRA_WIFI_STATE), anyInt())).thenReturn(WIFI_STATE_ENABLED); + mWifiStateWorker.onReceive(mContext, mIntent); + + assertThat(mWifiStateWorker.isWifiEnabled()).isTrue(); + } +} 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 7638452eaf90..9c12c5c7e2d6 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 @@ -1489,25 +1489,6 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test - public void testFinalizeFilteredChildrenPromotesSummary() { - // GIVEN a group with only one child was already drawn - addGroupSummary(0, PACKAGE_1, GROUP_1); - addGroupChild(1, PACKAGE_1, GROUP_1); - addGroupChild(2, PACKAGE_1, GROUP_1); - - // WHEN the parent is filtered out at the finalize step - mFinalizeFilter.mIndicesToFilter.add(1); - mFinalizeFilter.mIndicesToFilter.add(2); - - dispatchBuild(); - - // THEN the children should be promoted to the top level - verifyBuiltList( - notif(0) - ); - } - - @Test public void testFinalizeFilteredChildPromotesSibling() { // GIVEN a group with only one child was already drawn addGroupSummary(0, PACKAGE_1, GROUP_1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index c3658ba11b2d..663490ebfde0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -49,7 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController @Mock private lateinit var stackLayout: NotificationStackScrollLayout - private val testableResources = mContext.getOrCreateTestableResources() + private val testableResources = mContext.orCreateTestableResources private lateinit var sizeCalculator: NotificationStackSizeCalculator @@ -121,17 +121,16 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - fun computeHeight_returnsAtMostSpaceAvailable_withGapBeforeShelf() { + fun computeHeight_gapBeforeShelf_returnsSpaceUsed() { + // Each row in separate section. setGapHeight(gapHeight) - val shelfHeight = shelfHeight - val availableSpace = + val spaceUsed = listOf( - rowHeight + dividerHeight, - gapHeight + rowHeight + dividerHeight, - gapHeight + dividerHeight + shelfHeight) + dividerHeight + rowHeight, + dividerHeight + gapHeight + rowHeight, + dividerHeight + gapHeight + shelfHeight) .sum() - - // All rows in separate sections (default setup). + val availableSpace = spaceUsed + 1; val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) @@ -139,23 +138,29 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { assertThat(maxNotifications).isEqualTo(2) val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) - assertThat(height).isAtMost(availableSpace) + assertThat(height).isEqualTo(spaceUsed) } @Test - fun computeHeight_noGapBeforeShelf_returnsAtMostSpaceAvailable() { + fun computeHeight_noGapBeforeShelf_returnsSpaceUsed() { // Both rows are in the same section. setGapHeight(0f) + val rowHeight = rowHeight val shelfHeight = shelfHeight - val availableSpace = listOf(rowHeight + dividerHeight, dividerHeight + shelfHeight).sum() + val spaceUsed = + listOf( + dividerHeight + rowHeight, + dividerHeight + shelfHeight) + .sum() + val availableSpace = spaceUsed + 1 val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight)) val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) assertThat(maxNotifications).isEqualTo(1) val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) - assertThat(height).isAtMost(availableSpace) + assertThat(height).isEqualTo(spaceUsed) } @Test @@ -190,7 +195,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, onLockscreen = true) - assertThat(space).isEqualTo(5) + assertThat(space).isEqualTo(5 + dividerHeight) } @Test @@ -204,7 +209,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, onLockscreen = false) - assertThat(space).isEqualTo(10) + assertThat(space).isEqualTo(10 + dividerHeight) } private fun computeMaxKeyguardNotifications( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt index 01e95950e45a..80664013f95d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt @@ -1,8 +1,12 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager +import android.content.Context +import android.content.res.TypedArray import android.testing.AndroidTestingRunner +import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View +import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -13,7 +17,9 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController +import com.android.systemui.statusbar.policy.FakeConfigurationController import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -22,6 +28,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @@ -36,19 +43,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder @Mock private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var clock: TextView + @Mock private lateinit var date: TextView + @Mock private lateinit var carrierGroup: QSCarrierGroup @Mock private lateinit var batteryMeterView: BatteryMeterView @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController @Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var mockedContext: Context + @Mock private lateinit var typedArray: TypedArray + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE private lateinit var mLargeScreenShadeHeaderController: LargeScreenShadeHeaderController private lateinit var carrierIconSlots: List<String> + private val configurationController = FakeConfigurationController() @Before fun setup() { + whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock) + whenever(clock.context).thenReturn(mockedContext) + whenever(mockedContext.obtainStyledAttributes(anyInt(), any())).thenReturn(typedArray) + whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date) + whenever(date.context).thenReturn(mockedContext) + whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup) whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon)) .thenReturn(batteryMeterView) whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons) @@ -67,6 +87,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { view, statusBarIconController, privacyIconsController, + configurationController, qsCarrierGroupControllerBuilder, featureFlags, batteryMeterViewController, @@ -138,4 +159,38 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { mLargeScreenShadeHeaderController.active = true mLargeScreenShadeHeaderController.shadeExpanded = true } + + @Test + fun updateConfig_changesFontSize() { + val updatedTextPixelSize = 32 + setReturnTextSize(updatedTextPixelSize) + + configurationController.notifyDensityOrFontScaleChanged() + + verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat()) + verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat()) + verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) + } + + @Test + fun updateConfig_changesFontSizeMultipleTimes() { + val updatedTextPixelSize1 = 32 + setReturnTextSize(updatedTextPixelSize1) + configurationController.notifyDensityOrFontScaleChanged() + verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat()) + verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat()) + verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) + clearInvocations(carrierGroup) + + val updatedTextPixelSize2 = 42 + setReturnTextSize(updatedTextPixelSize2) + configurationController.notifyDensityOrFontScaleChanged() + verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat()) + verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat()) + verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status) + } + + private fun setReturnTextSize(resultTextSize: Int) { + whenever(typedArray.getDimensionPixelSize(anyInt(), anyInt())).thenReturn(resultTextSize) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt new file mode 100644 index 000000000000..2ff6dd43e84e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -0,0 +1,168 @@ +/* + * 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.statusbar.phone + +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.StatusBarIconView +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever + +/** + * Tests for {@link NotificationIconContainer}. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationIconContainerTest : SysuiTestCase() { + + private val iconContainer = NotificationIconContainer(context, /* attrs= */ null) + + @Test + fun calculateWidthFor_zeroIcons_widthIsZero() { + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f), + /* actual= */ 0f) + } + + @Test + fun calculateWidthFor_oneIcon_widthForOneIcon() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f), + /* actual= */ 30f) + } + + @Test + fun calculateWidthFor_fourIcons_widthForFourIcons() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f), + /* actual= */ 60f) + } + + @Test + fun calculateWidthFor_fiveIcons_widthForFourIcons() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f), + /* actual= */ 60f) + } + + @Test + fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + val icon = mockStatusBarIcon() + iconContainer.addView(icon) + assertEquals(1, iconContainer.childCount) + + val iconState = iconContainer.getIconState(icon) + iconState.iconAppearAmount = 1f + + val width = iconContainer.calculateWidthFor(/* numIcons= */ 1f) + iconContainer.setActualLayoutWidth(width.toInt()) + + iconContainer.calculateIconXTranslations() + assertEquals(10f, iconState.xTranslation) + assertFalse(iconContainer.hasOverflow()) + } + + @Test + fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + val iconOne = mockStatusBarIcon() + val iconTwo = mockStatusBarIcon() + val iconThree = mockStatusBarIcon() + val iconFour = mockStatusBarIcon() + + iconContainer.addView(iconOne) + iconContainer.addView(iconTwo) + iconContainer.addView(iconThree) + iconContainer.addView(iconFour) + assertEquals(4, iconContainer.childCount) + + val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f) + iconContainer.setActualLayoutWidth(width.toInt()) + + iconContainer.calculateIconXTranslations() + assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation) + assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation) + assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation) + assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation) + + assertFalse(iconContainer.hasOverflow()) + } + + @Test + fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() { + iconContainer.setActualPaddingStart(10f) + iconContainer.setActualPaddingEnd(10f) + iconContainer.setIconSize(10); + + val iconOne = mockStatusBarIcon() + val iconTwo = mockStatusBarIcon() + val iconThree = mockStatusBarIcon() + val iconFour = mockStatusBarIcon() + val iconFive = mockStatusBarIcon() + + iconContainer.addView(iconOne) + iconContainer.addView(iconTwo) + iconContainer.addView(iconThree) + iconContainer.addView(iconFour) + iconContainer.addView(iconFive) + assertEquals(5, iconContainer.childCount) + + val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f) + iconContainer.setActualLayoutWidth(width.toInt()) + + iconContainer.calculateIconXTranslations() + assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation) + assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation) + assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation) + assertTrue(iconContainer.hasOverflow()) + } + + private fun mockStatusBarIcon() : StatusBarIconView { + val iconView = mock(StatusBarIconView::class.java) + whenever(iconView.width).thenReturn(10) + + val icon = mock(android.graphics.drawable.Icon::class.java) + whenever(iconView.sourceIcon).thenReturn(icon) + + val sbn = mock(StatusBarNotification::class.java) + whenever(sbn.groupKey).thenReturn("groupKey") + whenever(iconView.notification).thenReturn(sbn) + return iconView + } +}
\ No newline at end of file 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 cad603c85bbc..f51c428be28a 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 @@ -352,6 +352,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private FalsingManagerFake mFalsingManager = new FalsingManagerFake(); private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private Handler mMainHandler; + private final PanelExpansionStateManager mPanelExpansionStateManager = + new PanelExpansionStateManager(); @Before public void setup() { @@ -516,7 +518,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mLargeScreenShadeHeaderController, mScreenOffAnimationController, mLockscreenGestureLogger, - new PanelExpansionStateManager(), + mPanelExpansionStateManager, mNotificationRemoteInputManager, mSysUIUnfoldComponent, mControlsComponent, @@ -1018,15 +1020,36 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testQsToBeImmediatelyExpandedInSplitShade() { + public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { enableSplitShade(/* enabled= */ true); + // set panel state to CLOSED + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0, + /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0); + assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); - mNotificationPanelViewController.onTrackingStarted(); + // change panel state to OPENING + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0.5f, + /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 100); assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue(); } @Test + public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() { + enableSplitShade(/* enabled= */ true); + // set panel state to CLOSED + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0, + /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0); + + // go to lockscreen, which also sets fraction to 1.0f and makes shade "expanded" + mStatusBarStateController.setState(KEYGUARD); + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, + /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 0); + + assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); + } + + @Test public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { mNotificationPanelViewController.mQs = mQs; when(mQsFrame.getX()).thenReturn(0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt index 146b56e49e65..16a326869562 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt @@ -23,6 +23,10 @@ class FakeConfigurationController : ConfigurationController { listeners.forEach { it.onThemeChanged() } } + fun notifyDensityOrFontScaleChanged() { + listeners.forEach { it.onDensityOrFontScaleChanged() } + } + fun notifyConfigurationChanged() { onConfigurationChanged(newConfiguration = null) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 3dfc94bcd5b6..c625dc7d4b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -729,6 +729,18 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); + reset(mResources); + when(mResources.getColor(eq(android.R.color.system_accent1_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6)); + when(mResources.getColor(eq(android.R.color.system_accent2_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6)); + when(mResources.getColor(eq(android.R.color.system_accent3_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6)); + when(mResources.getColor(eq(android.R.color.system_neutral1_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6)); + when(mResources.getColor(eq(android.R.color.system_neutral2_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6)); + // Defers event because we already have initial colors. verify(mThemeOverlayApplier, never()) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 5acae4859ee8..8fe57e18ea37 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -4414,7 +4414,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. - private final HashSet<String> mPrunedApps = new HashSet<>(); + private final SparseArray<Set<String>> mPrunedAppsPerUser = new SparseArray<>(); private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider = new HashMap<>(); @@ -4537,7 +4537,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // We're starting a new "system" restore operation, so any widget restore // state that we see from here on is intended to replace the current // widget configuration of any/all of the affected apps. - mPrunedApps.clear(); + getPrunedAppsLocked(userId).clear(); mUpdatesByProvider.clear(); mUpdatesByHost.clear(); } @@ -4934,8 +4934,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // instances that are hosted by that app, and (b) all instances in other hosts // for which 'pkg' is the provider. We assume that we'll be restoring all of // these hosts & providers, so will be reconstructing a correct live state. + @GuardedBy("mLock") private void pruneWidgetStateLocked(String pkg, int userId) { - if (!mPrunedApps.contains(pkg)) { + final Set<String> prunedApps = getPrunedAppsLocked(userId); + if (!prunedApps.contains(pkg)) { if (DEBUG) { Slog.i(TAG, "pruning widget state for restoring package " + pkg); } @@ -4958,7 +4960,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku removeWidgetLocked(widget); } } - mPrunedApps.add(pkg); + prunedApps.add(pkg); } else { if (DEBUG) { Slog.i(TAG, "already pruned " + pkg + ", continuing normally"); @@ -4966,6 +4968,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } + @GuardedBy("mLock") + @NonNull + private Set<String> getPrunedAppsLocked(int userId) { + if (!mPrunedAppsPerUser.contains(userId)) { + mPrunedAppsPerUser.set(userId, new ArraySet<>()); + } + return mPrunedAppsPerUser.get(userId); + } + private boolean isProviderAndHostInUser(Widget widget, int userId) { // Backup only widgets hosted or provided by the owner profile. return widget.host.getUserId() == userId && (widget.provider == null diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c5a4ca4dc0e5..91f6eeb875f6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1024,30 +1024,9 @@ public class ActivityManagerService extends IActivityManager.Stub private final ActivityMetricsLaunchObserver mActivityLaunchObserver = new ActivityMetricsLaunchObserver() { @Override - public void onActivityLaunched(byte[] activity, int temperature) { + public void onActivityLaunched(long id, ComponentName name, int temperature) { mAppProfiler.onActivityLaunched(); } - - // The other observer methods are unused - @Override - public void onIntentStarted(Intent intent, long timestampNs) { - } - - @Override - public void onIntentFailed() { - } - - @Override - public void onActivityLaunchCancelled(byte[] abortingActivity) { - } - - @Override - public void onActivityLaunchFinished(byte[] finalActivity, long timestampNs) { - } - - @Override - public void onReportFullyDrawn(byte[] finalActivity, long timestampNs) { - } }; private volatile boolean mBinderTransactionTrackingEnabled = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 71ae92aecbcf..3e5786eaa333 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -1200,8 +1200,19 @@ final class ActivityManagerShellCommand extends ShellCommand { } catch (NumberFormatException e) { packageName = arg; } - mInterface.crashApplicationWithType(-1, pid, packageName, userId, "shell-induced crash", - false, CrashedByAdbException.TYPE_ID); + + int[] userIds = (userId == UserHandle.USER_ALL) ? mInternal.mUserController.getUserIds() + : new int[]{userId}; + for (int id : userIds) { + if (mInternal.mUserController.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, id)) { + getOutPrintWriter().println( + "Shell does not have permission to crash packages for user " + id); + continue; + } + mInterface.crashApplicationWithType(-1, pid, packageName, id, "shell-induced crash", + false, CrashedByAdbException.TYPE_ID); + } return 0; } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 36c40a1b97dc..ed492bc7344c 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -33,6 +33,7 @@ import android.app.ActivityOptions; import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; +import android.app.RemoteServiceException.CrashedByAdbException; import android.app.usage.UsageStatsManager; import android.content.ActivityNotFoundException; import android.content.Context; @@ -523,6 +524,16 @@ class AppErrors { return; } + if (exceptionTypeId == CrashedByAdbException.TYPE_ID) { + String[] packages = proc.getPackageList(); + for (int i = 0; i < packages.length; i++) { + if (mService.mPackageManagerInt.isPackageStateProtected(packages[i], proc.userId)) { + Slog.w(TAG, "crashApplication: Can not crash protected package " + packages[i]); + return; + } + } + } + proc.scheduleCrashLocked(message, exceptionTypeId, extras); if (force) { // If the app is responsive, the scheduled crash will happen as expected diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index 672736df88f3..18fb6a480f29 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -64,9 +64,10 @@ public class DropboxRateLimiter { } if (now - errRecord.getStartTime() > RATE_LIMIT_BUFFER_DURATION) { + final int errCount = recentlyDroppedCount(errRecord); errRecord.setStartTime(now); errRecord.setCount(1); - return new RateLimitResult(false, recentlyDroppedCount(errRecord)); + return new RateLimitResult(false, errCount); } errRecord.incrementCount(); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index c48ff9f9f2cc..2dadcecc9f1f 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -97,6 +97,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, DeviceConfig.NAMESPACE_TETHERING, + DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, }; diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e14527098a72..dbe4fb8c8795 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -862,8 +862,8 @@ public class AudioDeviceInventory { } /*package*/ void disconnectLeAudio(int device) { - if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET || - device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) { + if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET + && device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) { Log.e(TAG, "disconnectLeAudio: Can't disconnect not LE Audio device " + device); return; } @@ -879,6 +879,8 @@ public class AudioDeviceInventory { new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") .record(); if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt(device, 0, + AudioSystem.DEVICE_NONE); toRemove.stream().forEach(deviceAddress -> makeLeAudioDeviceUnavailable(deviceAddress, device) ); diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index d10ed55281ef..0aa9a2bc4990 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -462,6 +462,7 @@ public class BtHelper { mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEADSET); mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEARING_AID); mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO); + mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO_BROADCAST); } // @GuardedBy("AudioDeviceBroker.mSetModeLock") @@ -687,6 +688,7 @@ public class BtHelper { case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: + case BluetoothProfile.LE_AUDIO_BROADCAST: mDeviceBroker.postBtProfileDisconnected(profile); break; diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 19a93f30937f..63609f77dc75 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -46,6 +46,7 @@ import java.util.Date; import java.util.Deque; import java.util.List; import java.util.Locale; +import java.util.function.Consumer; /** * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor} @@ -457,22 +458,33 @@ public class BiometricScheduler { } /** + * Get current operation <code>BaseClientMonitor</code> + * @deprecated TODO: b/229994966, encapsulate client monitors * @return the current operation */ + @Deprecated + @Nullable public BaseClientMonitor getCurrentClient() { return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null; } - /** The current operation if the requestId is set and matches. */ + /** + * The current operation if the requestId is set and matches. + * @deprecated TODO: b/229994966, encapsulate client monitors + */ @Deprecated @Nullable - public BaseClientMonitor getCurrentClientIfMatches(long requestId) { - if (mCurrentOperation != null) { - if (mCurrentOperation.isMatchingRequestId(requestId)) { - return mCurrentOperation.getClientMonitor(); + public void getCurrentClientIfMatches(long requestId, + @NonNull Consumer<BaseClientMonitor> clientMonitorConsumer) { + mHandler.post(() -> { + if (mCurrentOperation != null) { + if (mCurrentOperation.isMatchingRequestId(requestId)) { + clientMonitorConsumer.accept(mCurrentOperation.getClientMonitor()); + return; + } } - } - return null; + clientMonitorConsumer.accept(null); + }); } public int getCurrentPendingCount() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 7d5b77c2d711..998a8e1e9f90 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -582,35 +582,35 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { - final BaseClientMonitor client = - mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onPointerDown received during client: " + client); - return; - } - ((Udfps) client).onPointerDown(x, y, minor, major); + mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), "onPointerDown received during client: " + client); + return; + } + ((Udfps) client).onPointerDown(x, y, minor, major); + }); } @Override public void onPointerUp(long requestId, int sensorId) { - final BaseClientMonitor client = - mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onPointerUp received during client: " + client); - return; - } - ((Udfps) client).onPointerUp(); + mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), "onPointerUp received during client: " + client); + return; + } + ((Udfps) client).onPointerUp(); + }); } @Override public void onUiReady(long requestId, int sensorId) { - final BaseClientMonitor client = - mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onUiReady received during client: " + client); - return; - } - ((Udfps) client).onUiReady(); + mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), "onUiReady received during client: " + client); + return; + } + ((Udfps) client).onUiReady(); + }); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 52dbe2460e1c..78a30e820c80 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -794,32 +794,35 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @Override public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { - final BaseClientMonitor client = mScheduler.getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.w(TAG, "onFingerDown received during client: " + client); - return; - } - ((Udfps) client).onPointerDown(x, y, minor, major); + mScheduler.getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.w(TAG, "onFingerDown received during client: " + client); + return; + } + ((Udfps) client).onPointerDown(x, y, minor, major); + }); } @Override public void onPointerUp(long requestId, int sensorId) { - final BaseClientMonitor client = mScheduler.getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.w(TAG, "onFingerDown received during client: " + client); - return; - } - ((Udfps) client).onPointerUp(); + mScheduler.getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.w(TAG, "onFingerDown received during client: " + client); + return; + } + ((Udfps) client).onPointerUp(); + }); } @Override public void onUiReady(long requestId, int sensorId) { - final BaseClientMonitor client = mScheduler.getCurrentClientIfMatches(requestId); - if (!(client instanceof Udfps)) { - Slog.w(TAG, "onUiReady received during client: " + client); - return; - } - ((Udfps) client).onUiReady(); + mScheduler.getCurrentClientIfMatches(requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.w(TAG, "onUiReady received during client: " + client); + return; + } + ((Udfps) client).onUiReady(); + }); } @Override diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index f68d22acd6ab..4e1d899b26a6 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -17,13 +17,21 @@ package com.android.server.dreams; import static android.Manifest.permission.BIND_DREAM_SERVICE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; + +import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; 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.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; @@ -54,6 +62,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.wm.ActivityInterceptorCallback; import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; @@ -83,6 +92,7 @@ public final class DreamManagerService extends SystemService { private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; private final ComponentName mAmbientDisplayComponent; + private final boolean mDismissDreamOnActivityStart; private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; @@ -99,6 +109,26 @@ public final class DreamManagerService extends SystemService { private ComponentName mDreamOverlayServiceName; private AmbientDisplayConfiguration mDozeConfig; + private final ActivityInterceptorCallback mActivityInterceptorCallback = + new ActivityInterceptorCallback() { + @Nullable + @Override + public ActivityInterceptResult intercept(ActivityInterceptorInfo info) { + return null; + } + + @Override + public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo, + ActivityInterceptorInfo info) { + final int activityType = taskInfo.getActivityType(); + final boolean activityAllowed = activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_DREAM + || activityType == ACTIVITY_TYPE_ASSISTANT; + if (mCurrentDreamToken != null && !mCurrentDreamIsWaking && !activityAllowed) { + stopDreamInternal(false, "activity starting: " + activityInfo.name); + } + } + }; public DreamManagerService(Context context) { super(context); @@ -118,6 +148,8 @@ public final class DreamManagerService extends SystemService { mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent()); mDreamsOnlyEnabledForSystemUser = mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser); + mDismissDreamOnActivityStart = mContext.getResources().getBoolean( + R.bool.config_dismissDreamOnActivityStart); } @Override @@ -145,6 +177,12 @@ public final class DreamManagerService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE), false, mDozeEnabledObserver, UserHandle.USER_ALL); writePulseGestureEnabled(); + + if (mDismissDreamOnActivityStart) { + mAtmInternal.registerActivityStartInterceptor( + DREAM_MANAGER_ORDERED_ID, + mActivityInterceptorCallback); + } } } diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java index db2cb52d778e..5253d34a38f0 100644 --- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java +++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java @@ -20,7 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -34,7 +38,9 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Gets the service name using a framework resources, temporarily changing the service if necessary @@ -49,10 +55,14 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR /** Handler message to {@link #resetTemporaryService(int)} */ private static final int MSG_RESET_TEMPORARY_SERVICE = 0; - @NonNull private final Context mContext; - @NonNull private final Object mLock = new Object(); - @StringRes private final int mStringResourceId; - @ArrayRes private final int mArrayResourceId; + @NonNull + private final Context mContext; + @NonNull + private final Object mLock = new Object(); + @StringRes + private final int mStringResourceId; + @ArrayRes + private final int mArrayResourceId; private final boolean mIsMultiple; /** * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)}, @@ -71,7 +81,8 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR */ @GuardedBy("mLock") private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray(); - @Nullable private NameResolverListener mOnSetCallback; + @Nullable + private NameResolverListener mOnSetCallback; /** * When the temporary service will expire (and reset back to the default). */ @@ -160,10 +171,33 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR public String[] getDefaultServiceNameList(int userId) { synchronized (mLock) { if (mIsMultiple) { - return mContext.getResources().getStringArray(mArrayResourceId); + String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId); + // Filter out unimplemented services + // Initialize the validated array as null because we do not know the final size. + List<String> validatedServiceNameList = new ArrayList<>(); + try { + for (int i = 0; i < serviceNameList.length; i++) { + if (TextUtils.isEmpty(serviceNameList[i])) { + continue; + } + ComponentName serviceComponent = ComponentName.unflattenFromString( + serviceNameList[i]); + ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo( + serviceComponent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (serviceInfo != null) { + validatedServiceNameList.add(serviceNameList[i]); + } + } + } catch (Exception e) { + Slog.e(TAG, "Could not validate provided services.", e); + } + String[] validatedServiceNameArray = new String[validatedServiceNameList.size()]; + return validatedServiceNameList.toArray(validatedServiceNameArray); } else { final String name = mContext.getString(mStringResourceId); - return TextUtils.isEmpty(name) ? new String[0] : new String[] { name }; + return TextUtils.isEmpty(name) ? new String[0] : new String[]{name}; } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index b978131e175c..57d89dae588e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -133,10 +133,13 @@ public abstract class InputMethodManagerInternal { * * @param windowToken the window token that is now in control, or {@code null} if no client * window is in control of the IME. - * @param imeParentChanged {@code true} when the window manager thoughts the IME surface parent - * will end up to change later, or {@code false} otherwise. */ - public abstract void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged); + public abstract void reportImeControl(@Nullable IBinder windowToken); + + /** + * Indicates that the IME window has re-parented to the new target when the IME control changed. + */ + public abstract void onImeParentChanged(); /** * Destroys the IME surface. @@ -226,8 +229,11 @@ public abstract class InputMethodManagerInternal { } @Override - public void reportImeControl(@Nullable IBinder windowToken, - boolean imeParentChanged) { + public void reportImeControl(@Nullable IBinder windowToken) { + } + + @Override + public void onImeParentChanged() { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index ce8b9fabd5a0..6af00b3fbeea 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5700,19 +5700,23 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) { + public void reportImeControl(@Nullable IBinder windowToken) { synchronized (ImfLock.class) { if (mCurFocusedWindow != windowToken) { // mCurPerceptible was set by the focused window, but it is no longer in // control, so we reset mCurPerceptible. mCurPerceptible = true; } - if (imeParentChanged) { - // Hide the IME method menu earlier when the IME surface parent will change in - // case seeing the dialog dismiss flickering during the next focused window - // starting the input connection. - mMenuController.hideInputMethodMenu(); - } + } + } + + @Override + public void onImeParentChanged() { + synchronized (ImfLock.class) { + // Hide the IME method menu when the IME surface parent will change in + // case seeing the dialog dismiss flickering during the next focused window + // starting the input connection. + mMenuController.hideInputMethodMenu(); } } diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index 70a222fb09c5..dc5299077cc9 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -34,6 +34,8 @@ import java.util.Objects; public class SystemEmergencyHelper extends EmergencyHelper { private final Context mContext; + private final EmergencyCallTelephonyCallback mEmergencyCallTelephonyCallback = + new EmergencyCallTelephonyCallback(); TelephonyManager mTelephonyManager; @@ -56,7 +58,7 @@ public class SystemEmergencyHelper extends EmergencyHelper { // TODO: this doesn't account for multisim phones mTelephonyManager.registerTelephonyCallback(FgThread.getExecutor(), - new EmergencyCallTelephonyCallback()); + mEmergencyCallTelephonyCallback); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 21ee4c21ceeb..6078bfc95488 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -252,6 +252,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.StatsEvent; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -284,6 +285,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; +import com.android.internal.widget.LockPatternUtils; import com.android.server.DeviceIdleInternal; import com.android.server.EventLogTags; import com.android.server.IoThread; @@ -1923,6 +1925,54 @@ public class NotificationManagerService extends SystemService { private SettingsObserver mSettingsObserver; protected ZenModeHelper mZenModeHelper; + protected class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { + + SparseBooleanArray mUserInLockDownMode = new SparseBooleanArray(); + boolean mIsInLockDownMode = false; + + StrongAuthTracker(Context context) { + super(context); + } + + private boolean containsFlag(int haystack, int needle) { + return (haystack & needle) != 0; + } + + public boolean isInLockDownMode() { + return mIsInLockDownMode; + } + + @Override + public synchronized void onStrongAuthRequiredChanged(int userId) { + boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mUserInLockDownMode.put(userId, userInLockDownModeNext); + boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1; + + if (mIsInLockDownMode == isInLockDownModeNext) { + return; + } + + if (isInLockDownModeNext) { + cancelNotificationsWhenEnterLockDownMode(); + } + + // When the mIsInLockDownMode is true, both notifyPostedLocked and + // notifyRemovedLocked will be dismissed. So we shall call + // cancelNotificationsWhenEnterLockDownMode before we set mIsInLockDownMode + // as true and call postNotificationsWhenExitLockDownMode after we set + // mIsInLockDownMode as false. + mIsInLockDownMode = isInLockDownModeNext; + + if (!isInLockDownModeNext) { + postNotificationsWhenExitLockDownMode(); + } + } + } + + private LockPatternUtils mLockPatternUtils; + private StrongAuthTracker mStrongAuthTracker; + public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), @@ -1952,6 +2002,11 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setStrongAuthTracker(StrongAuthTracker strongAuthTracker) { + mStrongAuthTracker = strongAuthTracker; + } + + @VisibleForTesting void setKeyguardManager(KeyguardManager keyguardManager) { mKeyguardManager = keyguardManager; } @@ -2145,6 +2200,8 @@ public class NotificationManagerService extends SystemService { mPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + mLockPatternUtils = new LockPatternUtils(getContext()); + mStrongAuthTracker = new StrongAuthTracker(getContext()); mUiHandler = new Handler(UiThread.get().getLooper()); String[] extractorNames; try { @@ -2641,6 +2698,7 @@ public class NotificationManagerService extends SystemService { bubbsExtractor.setShortcutHelper(mShortcutHelper); } registerNotificationPreferencesPullers(); + mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. @@ -9537,6 +9595,29 @@ public class NotificationManagerService extends SystemService { } } + private void cancelNotificationsWhenEnterLockDownMode() { + synchronized (mNotificationLock) { + int numNotifications = mNotificationList.size(); + for (int i = 0; i < numNotifications; i++) { + NotificationRecord rec = mNotificationList.get(i); + mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, + rec.getStats()); + } + + } + } + + private void postNotificationsWhenExitLockDownMode() { + synchronized (mNotificationLock) { + int numNotifications = mNotificationList.size(); + for (int i = 0; i < numNotifications; i++) { + NotificationRecord rec = mNotificationList.get(i); + mListeners.notifyPostedLocked(rec, rec); + } + + } + } + private void updateNotificationPulse() { synchronized (mNotificationLock) { updateLightsLocked(); @@ -9753,6 +9834,10 @@ public class NotificationManagerService extends SystemService { rankings.toArray(new NotificationListenerService.Ranking[0])); } + boolean isInLockDownMode() { + return mStrongAuthTracker.isInLockDownMode(); + } + boolean hasCompanionDevice(ManagedServiceInfo info) { if (mCompanionManager == null) { mCompanionManager = getCompanionManager(); @@ -10804,8 +10889,12 @@ public class NotificationManagerService extends SystemService { * targetting <= O_MR1 */ @GuardedBy("mNotificationLock") - private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, + void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { + if (isInLockDownMode()) { + return; + } + try { // Lazily initialized snapshots of the notification. StatusBarNotification sbn = r.getSbn(); @@ -10903,6 +10992,10 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { + if (isInLockDownMode()) { + return; + } + final StatusBarNotification sbn = r.getSbn(); // make a copy in case changes are made to the underlying Notification object @@ -10948,6 +11041,10 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { + if (isInLockDownMode()) { + return; + } + boolean isHiddenRankingUpdate = changedHiddenNotifications != null && changedHiddenNotifications.size() > 0; // TODO (b/73052211): if the ranking update changed the notification type, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 2d8d4f588192..57a1fe04b690 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4187,8 +4187,8 @@ final class InstallPackageHelper { assertOverlayIsValid(pkg, parseFlags, scanFlags); } - // If the package is not on a system partition ensure it is signed with at least the - // minimum signature scheme version required for its target SDK. + // Ensure the package is signed with at least the minimum signature scheme version + // required for its target SDK. ScanPackageUtils.assertMinSignatureSchemeIsValid(pkg, parseFlags); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 78a600e34dae..6b10d4c17527 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -159,13 +159,14 @@ class PackageManagerShellCommand extends ShellCommand { private static final Map<String, Integer> SUPPORTED_PERMISSION_FLAGS = new ArrayMap<>(); private static final List<String> SUPPORTED_PERMISSION_FLAGS_LIST; static { + SUPPORTED_PERMISSION_FLAGS_LIST = List.of("review-required", "revoked-compat", + "revoke-when-requested", "user-fixed", "user-set"); SUPPORTED_PERMISSION_FLAGS.put("user-set", FLAG_PERMISSION_USER_SET); SUPPORTED_PERMISSION_FLAGS.put("user-fixed", FLAG_PERMISSION_USER_FIXED); SUPPORTED_PERMISSION_FLAGS.put("revoked-compat", FLAG_PERMISSION_REVOKED_COMPAT); SUPPORTED_PERMISSION_FLAGS.put("review-required", FLAG_PERMISSION_REVIEW_REQUIRED); SUPPORTED_PERMISSION_FLAGS.put("revoke-when-requested", FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); - SUPPORTED_PERMISSION_FLAGS_LIST = new ArrayList<>(SUPPORTED_PERMISSION_FLAGS.keySet()); } final IPackageManager mInterface; diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 4e8313bf1891..0dc188b75d5e 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -690,16 +690,14 @@ final class ScanPackageUtils { public static void assertMinSignatureSchemeIsValid(AndroidPackage pkg, @ParsingPackageUtils.ParseFlags int parseFlags) throws PackageManagerException { - if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { - int minSignatureSchemeVersion = - ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( - pkg.getTargetSdkVersion()); - if (pkg.getSigningDetails().getSignatureSchemeVersion() - < minSignatureSchemeVersion) { - throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No signature found in package of version " + minSignatureSchemeVersion - + " or newer for package " + pkg.getPackageName()); - } + int minSignatureSchemeVersion = + ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( + pkg.getTargetSdkVersion()); + if (pkg.getSigningDetails().getSignatureSchemeVersion() + < minSignatureSchemeVersion) { + throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + pkg.getPackageName()); } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a1b4b30c18cd..ba4d09f28d05 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -164,6 +164,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; @@ -633,8 +634,8 @@ public final class Settings implements Watchable, Snappable { runtimePermissionsPersistence, new Consumer<Integer>() { @Override public void accept(Integer userId) { - mRuntimePermissionsPersistence.writeStateForUser(userId, - mPermissionDataProvider, mPackages, mSharedUsers, mHandler, mLock); + mRuntimePermissionsPersistence.writeStateForUser(userId, mPermissionDataProvider, + mPackages, mSharedUsers, mHandler, mLock, /*sync=*/false); } }); mPermissionDataProvider = permissionDataProvider; @@ -5292,7 +5293,7 @@ public final class Settings implements Watchable, Snappable { public void writePermissionStateForUserLPr(int userId, boolean sync) { if (sync) { mRuntimePermissionsPersistence.writeStateForUser(userId, mPermissionDataProvider, - mPackages, mSharedUsers, /*handler=*/null, mLock); + mPackages, mSharedUsers, /*handler=*/null, mLock, /*sync=*/true); } else { mRuntimePermissionsPersistence.writeStateForUserAsync(userId); } @@ -5370,12 +5371,17 @@ public final class Settings implements Watchable, Snappable { } private static final class RuntimePermissionPersistence { - private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200; + // 200-400ms delay to avoid monopolizing PMS lock when written for multiple users. + private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 300; + private static final double WRITE_PERMISSIONS_DELAY_JITTER = 0.3; + private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000; private static final int UPGRADE_VERSION = -1; private static final int INITIAL_VERSION = 0; + private static final Random sRandom = new Random(); + private String mExtendedFingerprint; @GuardedBy("mPersistenceLock") @@ -5397,6 +5403,11 @@ public final class Settings implements Watchable, Snappable { private final SparseLongArray mLastNotWrittenMutationTimesMillis = new SparseLongArray(); @GuardedBy("mLock") + // Tracking the mutations that haven't yet been written to legacy state. + // This avoids unnecessary work when writing settings for multiple users. + private boolean mIsLegacyPermissionStateStale = false; + + @GuardedBy("mLock") // The mapping keys are user ids. private final SparseIntArray mVersions = new SparseIntArray(); @@ -5472,9 +5483,22 @@ public final class Settings implements Watchable, Snappable { return PackagePartitions.FINGERPRINT + "?pc_version=" + version; } + private static long uniformRandom(double low, double high) { + double mag = high - low; + return (long) (sRandom.nextDouble() * mag + low); + } + + private static long nextWritePermissionDelayMillis() { + final long delay = WRITE_PERMISSIONS_DELAY_MILLIS; + final double jitter = WRITE_PERMISSIONS_DELAY_JITTER; + return delay + uniformRandom(-jitter * delay, jitter * delay); + } + public void writeStateForUserAsync(int userId) { synchronized (mLock) { + mIsLegacyPermissionStateStale = true; final long currentTimeMillis = SystemClock.uptimeMillis(); + final long writePermissionDelayMillis = nextWritePermissionDelayMillis(); if (mWriteScheduled.get(userId)) { mAsyncHandler.removeMessages(userId); @@ -5493,7 +5517,7 @@ public final class Settings implements Watchable, Snappable { // Hold off a bit more as settings are frequently changing. final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0); - final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS, + final long writeDelayMillis = Math.min(writePermissionDelayMillis, maxDelayMillis); Message message = mAsyncHandler.obtainMessage(userId); @@ -5501,7 +5525,7 @@ public final class Settings implements Watchable, Snappable { } else { mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis); Message message = mAsyncHandler.obtainMessage(userId); - mAsyncHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS); + mAsyncHandler.sendMessageDelayed(message, writePermissionDelayMillis); mWriteScheduled.put(userId, true); } } @@ -5511,21 +5535,27 @@ public final class Settings implements Watchable, Snappable { legacyPermissionDataProvider, @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates, @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers, - @Nullable Handler pmHandler, @NonNull Object pmLock) { + @Nullable Handler pmHandler, @NonNull Object pmLock, + boolean sync) { final int version; final String fingerprint; + final boolean isLegacyPermissionStateStale; synchronized (mLock) { mAsyncHandler.removeMessages(userId); mWriteScheduled.delete(userId); version = mVersions.get(userId, INITIAL_VERSION); fingerprint = mFingerprints.get(userId); + isLegacyPermissionStateStale = mIsLegacyPermissionStateStale; + mIsLegacyPermissionStateStale = false; } Runnable writer = () -> { final RuntimePermissionsState runtimePermissions; synchronized (pmLock) { - legacyPermissionDataProvider.writeLegacyPermissionStateTEMP(); + if (sync || isLegacyPermissionStateStale) { + legacyPermissionDataProvider.writeLegacyPermissionStateTEMP(); + } Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 358e71a70550..0dabff8370ba 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -4291,7 +4291,7 @@ public class UserManagerService extends IUserManager.Stub { for (int i = 0; i < userSize; i++) { final UserData user = mUsers.valueAt(i); if (DBG) Slog.d(LOG_TAG, i + ":" + user.info.toFullString()); - if (user.info.preCreated && user.info.userType.equals(userType)) { + if (user.info.preCreated && !user.info.partial && user.info.userType.equals(userType)) { if (!user.info.isInitialized()) { Slog.w(LOG_TAG, "found pre-created user of type " + userType + ", but it's not initialized yet: " + user.info.toFullString()); 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 0311524cd768..a04f6d64ef8f 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -21,6 +21,7 @@ import static android.os.Process.FIRST_APPLICATION_UID; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.DownloadManager; import android.app.SearchManager; @@ -903,14 +904,6 @@ final class DefaultPermissionGrantPolicy { COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS); } - // Attention Service - String attentionServicePackageName = - mContext.getPackageManager().getAttentionServicePackageName(); - if (!TextUtils.isEmpty(attentionServicePackageName)) { - grantPermissionsToSystemPackage(pm, attentionServicePackageName, userId, - CAMERA_PERMISSIONS); - } - // There is no real "marker" interface to identify the shared storage backup, it is // hardcoded in BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE. grantSystemFixedPermissionsToSystemPackage(pm, "com.android.sharedstoragebackup", userId, @@ -1093,6 +1086,14 @@ final class DefaultPermissionGrantPolicy { } } + public void grantDefaultPermissionsToCarrierServiceApp(@NonNull String packageName, + @UserIdInt int userId) { + Log.i(TAG, "Grant permissions to Carrier Service app " + packageName + " for user:" + + userId); + grantPermissionsToPackage(NO_PM_CACHE, packageName, userId, /* ignoreSystemPackage */ false, + /* whitelistRestricted */ true, NOTIFICATION_PERMISSIONS); + } + private String getDefaultSystemHandlerActivityPackage(PackageManagerWrapper pm, String intentAction, int userId) { return getDefaultSystemHandlerActivityPackage(pm, new Intent(intentAction), userId); 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 ea554d3d7996..360a04f7e9bc 100644 --- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java @@ -18,6 +18,7 @@ package com.android.server.pm.permission; 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; @@ -248,6 +249,15 @@ public class LegacyPermissionManagerService extends ILegacyPermissionManager.Stu } @Override + public void grantDefaultPermissionsToCarrierServiceApp(@NonNull String packageName, + @UserIdInt int userId) { + PackageManagerServiceUtils.enforceSystemOrRoot( + "grantDefaultPermissionsForCarrierServiceApp"); + Binder.withCleanCallingIdentity(() -> mDefaultPermissionGrantPolicy + .grantDefaultPermissionsToCarrierServiceApp(packageName, userId)); + } + + @Override public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); PackageManagerServiceUtils.enforceSystemOrPhoneCaller( 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 092f3bec4012..d9e74f8a6afd 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -767,8 +767,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; - // REVIEW_REQUIRED can only be set by non-system apps for POST_NOTIFICATIONS, or by the - // shell or root UID. + // REVIEW_REQUIRED can be set on any permission by the shell or the root uid, or by + // any app for the POST_NOTIFICATIONS permission specifically. if (!POST_NOTIFICATIONS.equals(permName) && callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) { flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; 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 9897c42e4cec..e1ff9ead6740 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 @@ -3105,11 +3105,9 @@ public class ParsingPackageUtils { } final ParseResult<SigningDetails> verified; if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying; since the - // signature is not verified and some system apps can have their V2+ signatures - // stripped allow pulling the certs from the jar signature. + // systemDir APKs are already trusted, save time by not verifying verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(input, baseCodePath, - SigningDetails.SignatureSchemeVersion.JAR); + minSignatureScheme); } else { verified = ApkSignatureVerifier.verify(input, baseCodePath, minSignatureScheme); } diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING index ba4a62cdbbf1..8a1982a339ea 100644 --- a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING @@ -12,9 +12,6 @@ "name": "CtsDomainVerificationDeviceStandaloneTestCases" }, { - "name": "CtsDomainVerificationDeviceMultiUserTestCases" - }, - { "name": "CtsDomainVerificationHostTestCases" } ] diff --git a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java index 92b9944b74cf..77885c7ab8ba 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java @@ -74,10 +74,13 @@ public abstract class PermissionPolicyInternal { * * @param taskInfo The task to be checked * @param currPkg The package of the current top visible activity + * @param callingPkg The package that started the top visible activity * @param intent The intent of the current top visible activity + * @param activityName The name of the current top visible activity */ public abstract boolean shouldShowNotificationDialogForTask(@Nullable TaskInfo taskInfo, - @Nullable String currPkg, @Nullable Intent intent); + @Nullable String currPkg, @Nullable String callingPkg, @Nullable Intent intent, + @NonNull String activityName); /** * @return true if an intent will resolve to a permission request dialog activity diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 89ac9e773906..7ba1cadc5c8b 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -35,6 +35,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppOpsManager; @@ -66,11 +67,13 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.permission.LegacyPermissionManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -106,6 +109,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; /** @@ -163,6 +167,7 @@ public final class PermissionPolicyService extends SystemService { private PackageManagerInternal mPackageManagerInternal; private PermissionManagerServiceInternal mPermissionManagerInternal; private NotificationManagerInternal mNotificationManager; + private TelephonyManager mTelephonyManager; private final KeyguardManager mKeyguardManager; private final PackageManager mPackageManager; private final Handler mHandler; @@ -384,6 +389,13 @@ public final class PermissionPolicyService extends SystemService { public void onBootPhase(int phase) { if (DEBUG) Slog.i(LOG_TAG, "onBootPhase(" + phase + ")"); + if (phase == PHASE_DEVICE_SPECIFIC_SERVICES_READY) { + registerCarrierPrivilegesCallbacks(); + IntentFilter filter = + new IntentFilter(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); + mContext.registerReceiver(mSimConfigBroadcastReceiver, filter); + } + if (phase == PHASE_ACTIVITY_MANAGER_READY) { final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); @@ -408,6 +420,94 @@ public final class PermissionPolicyService extends SystemService { } + private void initTelephonyManagerIfNeeded() { + if (mTelephonyManager == null) { + mTelephonyManager = TelephonyManager.from(mContext); + } + } + + private void registerCarrierPrivilegesCallbacks() { + initTelephonyManagerIfNeeded(); + if (mTelephonyManager == null) { + return; + } + + int numPhones = mTelephonyManager.getActiveModemCount(); + for (int i = 0; i < numPhones; i++) { + PhoneCarrierPrivilegesCallback callback = new PhoneCarrierPrivilegesCallback(i); + mPhoneCarrierPrivilegesCallbacks.add(callback); + mTelephonyManager.registerCarrierPrivilegesCallback(i, mContext.getMainExecutor(), + callback); + } + } + + private void unregisterCarrierPrivilegesCallback() { + initTelephonyManagerIfNeeded(); + if (mTelephonyManager == null) { + return; + } + + for (int i = 0; i < mPhoneCarrierPrivilegesCallbacks.size(); i++) { + PhoneCarrierPrivilegesCallback callback = mPhoneCarrierPrivilegesCallbacks.get(i); + if (callback != null) { + mTelephonyManager.unregisterCarrierPrivilegesCallback(callback); + } + } + mPhoneCarrierPrivilegesCallbacks.clear(); + } + + private final class PhoneCarrierPrivilegesCallback + implements TelephonyManager.CarrierPrivilegesCallback { + private int mPhoneId; + + PhoneCarrierPrivilegesCallback(int phoneId) { + mPhoneId = phoneId; + } + @Override + public void onCarrierPrivilegesChanged( + @NonNull Set<String> privilegedPackageNames, + @NonNull Set<Integer> privilegedUids) { + initTelephonyManagerIfNeeded(); + if (mTelephonyManager == null) { + Log.e(LOG_TAG, "Cannot grant default permissions to Carrier Service app. " + + "TelephonyManager is null"); + return; + } + + String servicePkg = mTelephonyManager.getCarrierServicePackageNameForLogicalSlot( + mPhoneId); + if (servicePkg == null) { + return; + } + int[] users = LocalServices.getService(UserManagerInternal.class).getUserIds(); + LegacyPermissionManager legacyPermManager = + mContext.getSystemService(LegacyPermissionManager.class); + for (int i = 0; i < users.length; i++) { + try { + mPackageManager.getPackageInfoAsUser(servicePkg, 0, users[i]); + legacyPermManager.grantDefaultPermissionsToCarrierServiceApp( + servicePkg, users[i]); + } catch (PackageManager.NameNotFoundException e) { + // Do nothing if the package does not exist for the specified user + } + } + } + } + + private final ArrayList<PhoneCarrierPrivilegesCallback> mPhoneCarrierPrivilegesCallbacks = + new ArrayList<>(); + + private final BroadcastReceiver mSimConfigBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED.equals(intent.getAction())) { + return; + } + unregisterCarrierPrivilegesCallback(); + registerCarrierPrivilegesCallbacks(); + } + }; + /** * @return Whether the user is started but not yet stopped */ @@ -1067,7 +1167,8 @@ public final class PermissionPolicyService extends SystemService { ActivityInterceptorInfo info) { super.onActivityLaunched(taskInfo, activityInfo, info); if (!shouldShowNotificationDialogOrClearFlags(taskInfo, - activityInfo.packageName, info.intent, info.checkedOptions, true) + activityInfo.packageName, info.callingPackage, info.intent, + info.checkedOptions, activityInfo.name, true) || isNoDisplayActivity(activityInfo)) { return; } @@ -1138,9 +1239,9 @@ public final class PermissionPolicyService extends SystemService { @Override public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg, - Intent intent) { - return shouldShowNotificationDialogOrClearFlags( - taskInfo, currPkg, intent, null, false); + String callingPkg, Intent intent, String activityName) { + return shouldShowNotificationDialogOrClearFlags(taskInfo, currPkg, callingPkg, intent, + null, activityName, false); } private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo) { @@ -1166,23 +1267,61 @@ public final class PermissionPolicyService extends SystemService { * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or * 3. The activity belongs to the same package as the one which launched the task - * originally, and the task was started with a launcher intent + * originally, and the task was started with a launcher intent, or + * 4. The activity is the first activity in a new task, and was started by the app the + * activity belongs to, and that app has another task that is currently focused, which was + * started with a launcher intent. This case seeks to identify cases where an app launches, + * then immediately trampolines to a new activity and task. * @param taskInfo The task to be checked * @param currPkg The package of the current top visible activity + * @param callingPkg The package that initiated this dialog action * @param intent The intent of the current top visible activity + * @param options The ActivityOptions of the newly started activity, if this is called due + * to an activity start + * @param startedActivity The ActivityInfo of the newly started activity, if this is called + * due to an activity start */ private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg, - Intent intent, ActivityOptions options, boolean activityStart) { - if (intent == null || currPkg == null || taskInfo == null + String callingPkg, Intent intent, ActivityOptions options, + String topActivityName, boolean startedActivity) { + if (intent == null || currPkg == null || taskInfo == null || topActivityName == null || (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning) - && !activityStart)) { + && !startedActivity)) { return false; } - return isLauncherIntent(intent) || (options != null && options.isEligibleForLegacyPermissionPrompt()) - || (currPkg.equals(taskInfo.baseActivity.getPackageName()) - && isLauncherIntent(taskInfo.baseIntent)); + || isTaskStartedFromLauncher(currPkg, taskInfo) + || (isTaskPotentialTrampoline(topActivityName, currPkg, callingPkg, taskInfo, + intent) + && (!startedActivity || pkgHasRunningLauncherTask(currPkg, taskInfo))); + } + + private boolean isTaskPotentialTrampoline(String activityName, String currPkg, + String callingPkg, TaskInfo taskInfo, Intent intent) { + return currPkg.equals(callingPkg) && taskInfo.baseIntent.filterEquals(intent) + && taskInfo.numActivities == 1 + && activityName.equals(taskInfo.topActivityInfo.name); + } + + private boolean pkgHasRunningLauncherTask(String currPkg, TaskInfo taskInfo) { + ActivityTaskManagerInternal m = + LocalServices.getService(ActivityTaskManagerInternal.class); + try { + // TODO(b/230616478) Investigate alternatives like ActivityMetricsLaunchObserver + List<ActivityManager.AppTask> tasks = + m.getAppTasks(currPkg, mPackageManager.getPackageUid(currPkg, 0)); + for (int i = 0; i < tasks.size(); i++) { + TaskInfo other = tasks.get(i).getTaskInfo(); + if (other.taskId != taskInfo.taskId && other.isFocused && other.isRunning + && isTaskStartedFromLauncher(currPkg, other)) { + return true; + } + } + } catch (PackageManager.NameNotFoundException e) { + // Fall through + } + return false; } private boolean isLauncherIntent(Intent intent) { @@ -1193,6 +1332,11 @@ public final class PermissionPolicyService extends SystemService { || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER)); } + private boolean isTaskStartedFromLauncher(String currPkg, TaskInfo taskInfo) { + return currPkg.equals(taskInfo.baseActivity.getPackageName()) + && isLauncherIntent(taskInfo.baseIntent); + } + private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) { if ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user) & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index ae23b9e46d23..5db4a7b4a6f6 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -268,9 +268,17 @@ final class SpeechRecognitionManagerServiceImpl extends } private boolean componentMapsToRecognitionService(@NonNull ComponentName serviceComponent) { - List<ResolveInfo> resolveInfos = - getContext().getPackageManager().queryIntentServicesAsUser( - new Intent(RecognitionService.SERVICE_INTERFACE), 0, getUserId()); + List<ResolveInfo> resolveInfos; + + final long identityToken = Binder.clearCallingIdentity(); + try { + resolveInfos = + getContext().getPackageManager().queryIntentServicesAsUser( + new Intent(RecognitionService.SERVICE_INTERFACE), 0, getUserId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (resolveInfos == null) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java index 400460a1e656..34483957ca12 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -61,6 +61,7 @@ public abstract class ActivityInterceptorCallback { PERMISSION_POLICY_ORDERED_ID, INTENT_RESOLVER_ORDERED_ID, VIRTUAL_DEVICE_SERVICE_ORDERED_ID, + DREAM_MANAGER_ORDERED_ID, LAST_ORDERED_ID // Update this when adding new ids }) @Retention(RetentionPolicy.SOURCE) @@ -88,10 +89,15 @@ public abstract class ActivityInterceptorCallback { public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3; /** + * The identifier for {@link com.android.server.dreams.DreamManagerService} interceptor. + */ + public static final int DREAM_MANAGER_ORDERED_ID = 4; + + /** * The final id, used by the framework to determine the valid range of ids. Update this when * adding new ids. */ - static final int LAST_ORDERED_ID = VIRTUAL_DEVICE_SERVICE_ORDERED_ID; + static final int LAST_ORDERED_ID = DREAM_MANAGER_ORDERED_ID; /** * Data class for storing the various arguments needed for activity interception. diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java index c6b17e24b1de..81e5fbd564e0 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java @@ -18,17 +18,19 @@ package com.android.server.wm; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Intent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Observe activity manager launch sequences. + * Observe activity launch sequences. * - * The activity manager can have at most 1 concurrent launch sequences. Calls to this interface - * are ordered by a happens-before relation for each defined state transition (see below). + * Multiple calls to the callback methods can occur without first terminating the current launch + * sequence because activity can be launched concurrently. So the implementation should associate + * the corresponding event according to the timestamp from {@link #onIntentStarted} which is also + * used as the identifier to indicate which launch sequence it belongs to. * * When a new launch sequence is made, that sequence is in the {@code INTENT_STARTED} state which * is communicated by the {@link #onIntentStarted} callback. This is a transient state. @@ -47,7 +49,7 @@ import java.lang.annotation.RetentionPolicy; * Note this transition may not happen if the reportFullyDrawn event is not receivied, * in which case {@code FINISHED} is terminal. * - * Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't + * Note that the {@code ComponentName} provided as a parameter to some state transitions isn't * necessarily the same within a single launch sequence: it is only the top-most activity at the * time (if any). Trampoline activities coalesce several activity starts into a single launch * sequence. @@ -67,7 +69,7 @@ import java.lang.annotation.RetentionPolicy; * ╚════════════════╝ ╚═══════════════════════════╝ ╚═══════════════════════════╝ * </pre> */ -public interface ActivityMetricsLaunchObserver { +public class ActivityMetricsLaunchObserver { /** * The 'temperature' at which a launch sequence had started. * @@ -99,40 +101,31 @@ public interface ActivityMetricsLaunchObserver { public static final int TEMPERATURE_HOT = 3; /** - * Typedef marker that a {@code byte[]} actually contains an - * <a href="proto/android/server/activitymanagerservice.proto">ActivityRecordProto</a> - * in the protobuf format. - */ - @Retention(RetentionPolicy.SOURCE) - @interface ActivityRecordProto {} - - /** * Notifies the observer that a new launch sequence has begun as a result of a new intent. * * Once a launch sequence begins, the resolved activity will either subsequently start with * {@link #onActivityLaunched} or abort early (for example due to a resolution error or due to * a security error) with {@link #onIntentFailed}. * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. + * @param timestampNanos The timestamp when receiving the intent. It is also use as an + * identifier for other callback methods to known which launch sequence + * it is associated with. */ - public void onIntentStarted(@NonNull Intent intent, long timestampNanos); + public void onIntentStarted(@NonNull Intent intent, long timestampNanos) { + } /** * Notifies the observer that the current launch sequence has failed to launch an activity. * - * This function call terminates the current launch sequence. The next method call, if any, - * must be {@link #onIntentStarted}. + * This function call terminates the current launch sequence. * * Examples of this happening: * - Failure to resolve to an activity * - Calling package did not have the security permissions to call the requested activity * - Resolved activity was already running and only needed to be brought to the top - * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. */ - public void onIntentFailed(); + public void onIntentFailed(long id) { + } /** * Notifies the observer that the current launch sequence had begun starting an activity. @@ -145,62 +138,58 @@ public interface ActivityMetricsLaunchObserver { * necessarily the activity which will be considered as displayed when the activity * finishes launching (e.g. {@code activity} in {@link #onActivityLaunchFinished}). * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. + * @param id The timestamp as an identifier from {@link #onIntentStarted}. It may be a new id + * if the launching activity is started from an existing launch sequence (trampoline) + * but cannot coalesce to the existing one, e.g. to a different display. + * @param name The launching activity name. */ - public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, - @Temperature int temperature); + public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature) { + } /** * Notifies the observer that the current launch sequence has been aborted. * - * This function call terminates the current launch sequence. The next method call, if any, - * must be {@link #onIntentStarted}. + * This function call terminates the current launch sequence. * * This can happen for many reasons, for example the user switches away to another app * prior to the launch sequence completing, or the application being killed. * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. - * - * @param abortingActivity the last activity that had the top-most window during abort - * (this can be {@code null} in rare situations its unknown). + * @param id The timestamp as an identifier from {@link #onIntentStarted}. * * @apiNote The aborting activity isn't necessarily the same as the starting activity; * in the case of a trampoline, multiple activities could've been started * and only the latest activity is reported here. */ - public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] abortingActivity); + public void onActivityLaunchCancelled(long id) { + } /** * Notifies the observer that the current launch sequence has been successfully finished. * - * This function call terminates the current launch sequence. The next method call, if any, - * must be {@link #onIntentStarted}. + * This function call terminates the current launch sequence. * * A launch sequence is considered to be successfully finished when a frame is fully * drawn for the first time: the top-most activity at the time is what's reported here. * - * @param finalActivity the top-most activity whose windows were first to fully draw + * @param id The timestamp as an identifier from {@link #onIntentStarted}. + * @param name The name of drawn activity. It can be different from {@link #onActivityLaunched} + * if the transition contains multiple launching activities (e.g. trampoline). * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds. * To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted} * from {@code timestampNanos}. * - * Multiple calls to this method cannot occur without first terminating the current - * launch sequence. - * * @apiNote The finishing activity isn't necessarily the same as the starting activity; * in the case of a trampoline, multiple activities could've been started * and only the latest activity that was top-most during first-frame drawn * is reported here. */ - public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity, - long timestampNanos); + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos) { + } /** * Notifies the observer that the application self-reported itself as being fully drawn. * - * @param activity the activity that triggers the ReportFullyDrawn event. + * @param id The timestamp as an identifier from {@link #onIntentStarted}. * @param timestampNanos the timestamp of ReportFullyDrawn event in nanoseconds. * To compute the duration, deduct the deduct the timestamp {@link #onIntentStarted} * from {@code timestampNanos}. @@ -209,7 +198,7 @@ public interface ActivityMetricsLaunchObserver { * It is used as an accurate estimate of meanfully app startup time. * This event may be missing for many apps. */ - public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, - long timestampNanos); + public void onReportFullyDrawn(long id, long timestampNanos) { + } } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 7f84f61a91ff..1ea08f5cfea2 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -97,7 +97,6 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -176,7 +175,6 @@ class ActivityMetricsLogger { * in-order on the same thread to fulfill the "happens-before" guarantee in LaunchObserver. */ private final LaunchObserverRegistryImpl mLaunchObserver; - @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512; private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>(); private AppHibernationManagerInternal mAppHibernationManagerInternal; @@ -675,7 +673,7 @@ class ActivityMetricsLogger { launchObserverNotifyActivityLaunched(newInfo); } else { // As abort for no process switch. - launchObserverNotifyIntentFailed(); + launchObserverNotifyIntentFailed(newInfo.mTransitionStartTimeNs); } scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity); @@ -910,7 +908,7 @@ class ActivityMetricsLogger { } if (DEBUG_METRICS) Slog.i(TAG, "abort launch cause=" + cause); state.stopTrace(true /* abort */); - launchObserverNotifyIntentFailed(); + launchObserverNotifyIntentFailed(state.mCurrentTransitionStartTimeNs); } /** Aborts tracking of current launch metrics. */ @@ -1187,7 +1185,7 @@ class ActivityMetricsLogger { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // Notify reportFullyDrawn event. - launchObserverNotifyReportFullyDrawn(r, currentTimestampNs); + launchObserverNotifyReportFullyDrawn(info, currentTimestampNs); return infoSnapshot; } @@ -1531,11 +1529,11 @@ class ActivityMetricsLogger { * aborted due to intent failure (e.g. intent resolve failed or security error, etc) or * intent being delivered to the top running activity. */ - private void launchObserverNotifyIntentFailed() { + private void launchObserverNotifyIntentFailed(long id) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyIntentFailed"); - mLaunchObserver.onIntentFailed(); + mLaunchObserver.onIntentFailed(id); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1552,8 +1550,8 @@ class ActivityMetricsLogger { convertTransitionTypeToLaunchObserverTemperature(info.mTransitionType); // Beginning a launch is timing sensitive and so should be observed as soon as possible. - mLaunchObserver.onActivityLaunched(convertActivityRecordToProto(info.mLastLaunchedActivity), - temperature); + mLaunchObserver.onActivityLaunched(info.mTransitionStartTimeNs, + info.mLastLaunchedActivity.mActivityComponent, temperature); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1561,10 +1559,10 @@ class ActivityMetricsLogger { /** * Notifies the {@link ActivityMetricsLaunchObserver} the reportFullDrawn event. */ - private void launchObserverNotifyReportFullyDrawn(ActivityRecord r, long timestampNs) { + private void launchObserverNotifyReportFullyDrawn(TransitionInfo info, long timestampNs) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyReportFullyDrawn"); - mLaunchObserver.onReportFullyDrawn(convertActivityRecordToProto(r), timestampNs); + mLaunchObserver.onReportFullyDrawn(info.mTransitionStartTimeNs, timestampNs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1576,10 +1574,7 @@ class ActivityMetricsLogger { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyActivityLaunchCancelled"); - final @ActivityMetricsLaunchObserver.ActivityRecordProto byte[] activityRecordProto = - info != null ? convertActivityRecordToProto(info.mLastLaunchedActivity) : null; - - mLaunchObserver.onActivityLaunchCancelled(activityRecordProto); + mLaunchObserver.onActivityLaunchCancelled(info.mTransitionStartTimeNs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1592,31 +1587,10 @@ class ActivityMetricsLogger { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "MetricsLogger:launchObserverNotifyActivityLaunchFinished"); - mLaunchObserver.onActivityLaunchFinished( - convertActivityRecordToProto(info.mLastLaunchedActivity), timestampNs); - - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } - - @VisibleForTesting - static @ActivityMetricsLaunchObserver.ActivityRecordProto byte[] - convertActivityRecordToProto(ActivityRecord record) { - // May take non-negligible amount of time to convert ActivityRecord into a proto, - // so track the time. - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - "MetricsLogger:convertActivityRecordToProto"); - - // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, - // so create a new one every time. - final ProtoOutputStream protoOutputStream = - new ProtoOutputStream(LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); - // Write this data out as the top-most ActivityRecordProto (i.e. it is not a sub-object). - record.dumpDebug(protoOutputStream, WindowTraceLogLevel.ALL); - final byte[] bytes = protoOutputStream.getBytes(); + mLaunchObserver.onActivityLaunchFinished(info.mTransitionStartTimeNs, + info.mLastLaunchedActivity.mActivityComponent, timestampNs); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - - return bytes; } private static @ActivityMetricsLaunchObserver.Temperature int diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b6a1784839de..d00d8b8c9f8a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -661,10 +661,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * The activity is opaque and fills the entire space of this task. - * @see WindowContainer#fillsParent() + * @see #occludesParent() */ private boolean mOccludesParent; + /** + * Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute + * from the style of activity. Because we don't want {@link WindowContainer#getOrientation()} + * to be affected by the temporal state of {@link ActivityClientController#convertToTranslucent} + * when running ANIM_SCENE_TRANSITION. + * @see WindowContainer#fillsParent() + */ + private final boolean mFillsParent; + // The input dispatching timeout for this application token in milliseconds. long mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; @@ -1956,8 +1965,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // This style is propagated to the main window attributes with // FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout. || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); + mFillsParent = mOccludesParent; noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); } else { + mFillsParent = mOccludesParent = true; noDisplay = false; } @@ -2436,6 +2447,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean activityAllDrawn, TaskSnapshot snapshot) { + // A special case that a new activity is launching to an existing task which is moving to + // front. If the launching activity is the one that started the task, it could be a + // trampoline that will be always created and finished immediately. Then give a chance to + // see if the snapshot is usable for the current running activity so the transition will + // look smoother, instead of showing a splash screen on the second launch. + if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null + && mActivityComponent.equals(task.intent.getComponent())) { + final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess); + if (topAttached != null && topAttached.isSnapshotCompatible(snapshot)) { + return STARTING_WINDOW_TYPE_SNAPSHOT; + } + } final boolean isActivityHome = isActivityTypeHome(); if ((newTask || !processRunning || (taskSwitch && !activityCreated)) && !isActivityHome) { @@ -2852,7 +2875,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean fillsParent() { - return occludesParent(true /* includingFinishing */); + return mFillsParent; } /** Returns true if this activity is not finishing, is opaque and fills the entire space of diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d254aaff1a1c..41b724f2596f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -64,6 +64,7 @@ import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS; import static android.provider.Settings.System.FONT_SCALE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManager.TRANSIT_WAKE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; @@ -3398,6 +3399,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final long token = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { + // Keyguard asked us to clear the home task snapshot before going away, so do that. + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) { + mActivityClientController.invalidateHomeTaskSnapshot(null /* token */); + } + mRootWindowContainer.forAllDisplays(displayContent -> { mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags); }); diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 6e46fa6b67d0..e80c2607a0ad 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -48,8 +49,9 @@ class AppTaskImpl extends IAppTask.Stub { mCallingUid = callingUid; } - private void checkCaller() { - if (mCallingUid != Binder.getCallingUid()) { + private void checkCallerOrSystemOrRoot() { + if (mCallingUid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid() + && Process.ROOT_UID != Binder.getCallingUid()) { throw new SecurityException("Caller " + mCallingUid + " does not match caller of getAppTasks(): " + Binder.getCallingUid()); } @@ -67,7 +69,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void finishAndRemoveTask() { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); @@ -85,7 +87,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public ActivityManager.RecentTaskInfo getTaskInfo() { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); @@ -105,7 +107,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void moveToFront(IApplicationThread appThread, String callingPackage) { - checkCaller(); + checkCallerOrSystemOrRoot(); // Will bring task to front if it already has a root activity. final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -136,7 +138,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, Intent intent, String resolvedType, Bundle bOptions) { - checkCaller(); + checkCallerOrSystemOrRoot(); mService.assertPackageMatchesCallingUid(callingPackage); int callingUser = UserHandle.getCallingUserId(); @@ -167,7 +169,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void setExcludeFromRecents(boolean exclude) { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 68a09a6d4b9b..5410dd8508f1 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; @@ -683,6 +684,9 @@ public class AppTransition implements Dump { } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); + if (mNextAppTransitionBackgroundColor != 0) { + a.setBackdropColor(mNextAppTransitionBackgroundColor); + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s " + "isEntrance=%b Callers=%s", @@ -842,10 +846,6 @@ public class AppTransition implements Dump { } setAppTransitionFinishedCallbackIfNeeded(a); - if (mNextAppTransitionBackgroundColor != 0) { - a.setBackdropColor(mNextAppTransitionBackgroundColor); - } - return a; } @@ -1259,6 +1259,9 @@ public class AppTransition implements Dump { "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER")); sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION, "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION")); + sFlagToString.add(new Pair<>( + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT, + "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_WITH_IN_WINDOW_ANIMATIONS")); sFlagToString.add(new Pair<>(TRANSIT_FLAG_APP_CRASHED, "TRANSIT_FLAG_APP_CRASHED")); sFlagToString.add(new Pair<>(TRANSIT_FLAG_OPEN_BEHIND, diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 3bda2e60334a..701fc9441acb 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -912,6 +912,15 @@ public class AppTransitionController { canPromote = false; } + // If the current window container is task and it have adjacent task, it means + // both tasks will open or close app toghther but we want get their opening or + // closing animation target independently so do not promote. + if (current.asTask() != null + && current.asTask().getAdjacentTaskFragment() != null + && current.asTask().getAdjacentTaskFragment().asTask() != null) { + canPromote = false; + } + // Find all siblings of the current WindowContainer in "candidates", move them into // a separate list "siblings", and checks if an animation target can be promoted // to its parent. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index dc441860f7c8..ed1bbf8e4b74 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -60,6 +60,7 @@ import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; +import static android.view.WindowManager.LayoutParams; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -4291,18 +4292,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update Ime parent when IME insets leash created or the new IME layering target might // updated from setImeLayeringTarget, which is the best time that default IME visibility // has been settled down after IME control target changed. - final boolean imeParentChanged = - prevImeControlTarget != mImeControlTarget || forceUpdateImeParent; - if (imeParentChanged) { + final boolean imeControlChanged = prevImeControlTarget != mImeControlTarget; + if (imeControlChanged || forceUpdateImeParent) { updateImeParent(); } final WindowState win = InsetsControlTarget.asWindowOrNull(mImeControlTarget); final IBinder token = win != null ? win.mClient.asBinder() : null; // Note: not allowed to call into IMMS with the WM lock held, hence the post. - mWmService.mH.post(() -> - InputMethodManagerInternal.get().reportImeControl(token, imeParentChanged) - ); + mWmService.mH.post(() -> InputMethodManagerInternal.get().reportImeControl(token)); } void updateImeParent() { @@ -4324,6 +4322,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // do a force update to make sure there is a layer set for the new parent. assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */); scheduleAnimation(); + + mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged()); } } @@ -4347,10 +4347,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @VisibleForTesting SurfaceControl computeImeParent() { - if (mImeLayeringTarget != null && mImeInputTarget != null - && mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) { - // Do not change parent if the window hasn't requested IME. - return null; + if (mImeLayeringTarget != null) { + // Ensure changing the IME parent when the layering target that may use IME has + // became to the input target for preventing IME flickers. + // Note that: + // 1) For the imeLayeringTarget that may not use IME but requires IME on top + // of it (e.g. an overlay window with NOT_FOCUSABLE|ALT_FOCUSABLE_IM flags), we allow + // it to re-parent the IME on top the display to keep the legacy behavior. + // 2) Even though the starting window won't use IME, the associated activity + // behind the starting window may request the input. If so, then we should still hold + // the IME parent change until the activity started the input. + boolean imeLayeringTargetMayUseIme = + LayoutParams.mayUseInputMethod(mImeLayeringTarget.mAttrs.flags) + || mImeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING; + if (imeLayeringTargetMayUseIme && mImeInputTarget != null + && mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) { + // Do not change parent if the window hasn't requested IME. + return null; + } } // Attach it to app if the target is part of an app and such app is covering the entire // screen. If it's not covering the entire screen the IME might extend beyond the apps diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 76aa7f963aa6..fd0631320520 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -83,7 +83,7 @@ public class DisplayFrames { final Rect safe = mDisplayCutoutSafe; final DisplayCutout cutout = displayCutout.getDisplayCutout(); if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight - && mRotation != info.rotation + && mRotation == info.rotation && state.getDisplayCutout().equals(cutout) && state.getRoundedCorners().equals(roundedCorners) && state.getPrivacyIndicatorBounds().equals(indicatorBounds)) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 014bb9f5e6ad..62998cb6bc40 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -300,10 +300,6 @@ public class DisplayPolicy { // needs to be opaque. private WindowState mNavBarBackgroundWindow; - // The window that draws fake rounded corners and should provide insets to calculate the correct - // rounded corner insets. - private WindowState mRoundedCornerWindow; - /** * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies * which appearance. @@ -970,16 +966,10 @@ public class DisplayPolicy { mExtraNavBarAltPosition = getAltBarPosition(attrs); } - if (attrs.insetsRoundedCornerFrame) { - // Currently, only support one rounded corner window which is the TaskBar. - if (mRoundedCornerWindow != null && mRoundedCornerWindow != win) { - throw new IllegalArgumentException("Found multiple rounded corner window :" - + " current = " + mRoundedCornerWindow - + " new = " + win); - } - mRoundedCornerWindow = win; - } else if (mRoundedCornerWindow == win) { - mRoundedCornerWindow = null; + final InsetsSourceProvider provider = win.getControllableInsetProvider(); + if (provider != null && provider.getSource().getInsetsRoundedCornerFrame() + != attrs.insetsRoundedCornerFrame) { + provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame); } } @@ -1326,9 +1316,6 @@ public class DisplayPolicy { if (mLastFocusedWindow == win) { mLastFocusedWindow = null; } - if (mRoundedCornerWindow == win) { - mRoundedCornerWindow = null; - } mInsetsSourceWindowsExceptIme.remove(win); } @@ -1360,10 +1347,6 @@ public class DisplayPolicy { return mNavigationBar != null ? mNavigationBar : mNavigationBarAlt; } - WindowState getRoundedCornerWindow() { - return mRoundedCornerWindow; - } - /** * Control the animation to run when a window's state changes. Return a positive number to * force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 5aacb094207e..f833773cd5c6 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -702,17 +702,17 @@ public class DisplayRotation { } boolean canRotateSeamlessly(int oldRotation, int newRotation) { + // If the navigation bar can't change sides, then it will jump when we change orientations + // and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation + // where the navbar is low-profile enough that this isn't very noticeable. + if (mAllowSeamlessRotationDespiteNavBarMoving || mDisplayPolicy.navigationBarCanMove()) { + return true; + } // For the upside down rotation we don't rotate seamlessly as the navigation bar moves // position. Note most apps (using orientation:sensor or user as opposed to fullSensor) // will not enter the reverse portrait orientation, so actually the orientation won't change // at all. - if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) { - return false; - } - // If the navigation bar can't change sides, then it will jump when we change orientations - // and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation - // where the navbar is low-profile enough that this isn't very noticeable. - return mAllowSeamlessRotationDespiteNavBarMoving || mDisplayPolicy.navigationBarCanMove(); + return oldRotation != Surface.ROTATION_180 && newRotation != Surface.ROTATION_180; } void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) { @@ -1224,16 +1224,8 @@ public class DisplayRotation { || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { // Otherwise, use sensor only if requested by the application or enabled // by default for USER or UNSPECIFIED modes. Does not apply to NOSENSOR. - if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) { - // Can't read this during init() because the context doesn't have display metrics at - // that time so we cannot determine tablet vs. phone then. - mAllowAllRotations = mContext.getResources().getBoolean( - R.bool.config_allowAllRotations) - ? ALLOW_ALL_ROTATIONS_ENABLED - : ALLOW_ALL_ROTATIONS_DISABLED; - } if (sensorRotation != Surface.ROTATION_180 - || mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED + || getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) { preferredRotation = sensorRotation; @@ -1322,6 +1314,19 @@ public class DisplayRotation { } } + private int getAllowAllRotations() { + if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) { + // Can't read this during init() because the context doesn't have display metrics at + // that time so we cannot determine tablet vs. phone then. + mAllowAllRotations = mContext.getResources().getBoolean( + R.bool.config_allowAllRotations) + ? ALLOW_ALL_ROTATIONS_ENABLED + : ALLOW_ALL_ROTATIONS_DISABLED; + } + + return mAllowAllRotations; + } + private boolean isLandscapeOrSeascape(int rotation) { return rotation == mLandscapeRotation || rotation == mSeascapeRotation; } @@ -1349,6 +1354,11 @@ public class DisplayRotation { case ActivityInfo.SCREEN_ORIENTATION_USER: case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: + // When all rotations enabled it works with any of the 4 rotations + if (getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED) { + return preferredRotation >= 0; + } + // Works with any rotation except upside down. return (preferredRotation >= 0) && (preferredRotation != Surface.ROTATION_180); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 6162f12b516e..5c8cfffdd3b3 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -46,7 +46,6 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.StatusBarManager; import android.app.WindowConfiguration; -import android.graphics.Insets; import android.graphics.Rect; import android.util.ArrayMap; import android.util.IntArray; @@ -461,22 +460,10 @@ class InsetsPolicy { private InsetsState adjustInsetsForRoundedCorners(WindowState w, InsetsState originalState, boolean copyState) { - final WindowState roundedCornerWindow = mPolicy.getRoundedCornerWindow(); final Task task = w.getTask(); - if (task != null && !task.getWindowConfiguration().tasksAreFloating() - && (roundedCornerWindow != null || task.inSplitScreen())) { - // Instead of using display frame to calculating rounded corner, for the fake rounded - // corners drawn by divider bar or task bar, we need to re-calculate rounded corners - // based on task bounds and if the task bounds is intersected with task bar, we should - // exclude the intersected part. + if (task != null && !task.getWindowConfiguration().tasksAreFloating()) { + // Use task bounds to calculating rounded corners if the task is not floating. final Rect roundedCornerFrame = new Rect(task.getBounds()); - if (roundedCornerWindow != null - && roundedCornerWindow.getControllableInsetProvider() != null) { - final InsetsSource source = - roundedCornerWindow.getControllableInsetProvider().getSource(); - final Insets insets = source.calculateInsets(roundedCornerFrame, false); - roundedCornerFrame.inset(insets); - } final InsetsState state = copyState ? new InsetsState(originalState) : originalState; state.setRoundedCornerFrame(roundedCornerFrame); return state; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 8413c5442536..9853d1304b14 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -170,6 +170,7 @@ abstract class InsetsSourceProvider { if (windowContainer == null) { setServerVisible(false); mSource.setVisibleFrame(null); + mSource.setInsetsRoundedCornerFrame(false); mSourceFrame.setEmpty(); } else { mWindowContainer.getProvidedInsetsSources().put(mSource.getType(), mSource); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 2ebb59751634..f36dbfa2316e 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -23,6 +23,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; @@ -32,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; @@ -309,6 +311,10 @@ class KeyguardController { if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS) != 0) { result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; } + if ((keyguardGoingAwayFlags + & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) { + result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT; + } return result; } diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java index 362ed3c380c5..9cbc1bdcbeeb 100644 --- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java +++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java @@ -16,12 +16,11 @@ package com.android.server.wm; +import android.content.ComponentName; import android.content.Intent; import android.os.Handler; import android.os.Looper; -import android.os.Message; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; @@ -39,8 +38,8 @@ import java.util.ArrayList; * * @see ActivityTaskManagerInternal#getLaunchObserverRegistry() */ -class LaunchObserverRegistryImpl implements - ActivityMetricsLaunchObserverRegistry, ActivityMetricsLaunchObserver { +class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implements + ActivityMetricsLaunchObserverRegistry { private final ArrayList<ActivityMetricsLaunchObserver> mList = new ArrayList<>(); /** @@ -79,45 +78,36 @@ class LaunchObserverRegistryImpl implements } @Override - public void onIntentFailed() { + public void onIntentFailed(long id) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnIntentFailed, this)); + LaunchObserverRegistryImpl::handleOnIntentFailed, this, id)); } @Override - public void onActivityLaunched( - @ActivityRecordProto byte[] activity, - int temperature) { + public void onActivityLaunched(long id, ComponentName name, int temperature) { mHandler.sendMessage(PooledLambda.obtainMessage( LaunchObserverRegistryImpl::handleOnActivityLaunched, - this, activity, temperature)); + this, id, name, temperature)); } @Override - public void onActivityLaunchCancelled( - @ActivityRecordProto byte[] activity) { + public void onActivityLaunchCancelled(long id) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnActivityLaunchCancelled, this, activity)); + LaunchObserverRegistryImpl::handleOnActivityLaunchCancelled, this, id)); } @Override - public void onActivityLaunchFinished( - @ActivityRecordProto byte[] activity, - long timestampNs) { + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, - this, - activity, - timestampNs)); + LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, + this, id, name, timestampNs)); } @Override - public void onReportFullyDrawn(@ActivityRecordProto byte[] activity, long timestampNs) { + public void onReportFullyDrawn(long id, long timestampNs) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnReportFullyDrawn, - this, - activity, - timestampNs)); + LaunchObserverRegistryImpl::handleOnReportFullyDrawn, + this, id, timestampNs)); } // Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be @@ -135,53 +125,43 @@ class LaunchObserverRegistryImpl implements private void handleOnIntentStarted(Intent intent, long timestampNs) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onIntentStarted(intent, timestampNs); + mList.get(i).onIntentStarted(intent, timestampNs); } } - private void handleOnIntentFailed() { + private void handleOnIntentFailed(long id) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onIntentFailed(); + mList.get(i).onIntentFailed(id); } } - private void handleOnActivityLaunched( - @ActivityRecordProto byte[] activity, + private void handleOnActivityLaunched(long id, ComponentName name, @Temperature int temperature) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onActivityLaunched(activity, temperature); + mList.get(i).onActivityLaunched(id, name, temperature); } } - private void handleOnActivityLaunchCancelled( - @ActivityRecordProto byte[] activity) { + private void handleOnActivityLaunchCancelled(long id) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onActivityLaunchCancelled(activity); + mList.get(i).onActivityLaunchCancelled(id); } } - private void handleOnActivityLaunchFinished( - @ActivityRecordProto byte[] activity, long timestampNs) { + private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onActivityLaunchFinished(activity, timestampNs); + mList.get(i).onActivityLaunchFinished(id, name, timestampNs); } } - private void handleOnReportFullyDrawn( - @ActivityRecordProto byte[] activity, long timestampNs) { + private void handleOnReportFullyDrawn(long id, long timestampNs) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - ActivityMetricsLaunchObserver o = mList.get(i); - o.onReportFullyDrawn(activity, timestampNs); + mList.get(i).onReportFullyDrawn(id, timestampNs); } } } diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java index 11a27c593d9e..3f6fb622481f 100644 --- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -16,15 +16,11 @@ package com.android.server.wm; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_270; - import android.hardware.display.DisplayManagerInternal; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.view.DisplayInfo; -import android.view.Surface; import java.util.Set; @@ -44,8 +40,7 @@ public class PossibleDisplayInfoMapper { /** * Map of all logical displays, indexed by logical display id. - * Each logical display has multiple entries, one for each possible rotation and device - * state. + * Each logical display has multiple entries, one for each device state. * * Emptied and re-calculated when a display is added, removed, or changed. */ @@ -57,8 +52,8 @@ public class PossibleDisplayInfoMapper { /** - * Returns, for the given displayId, a set of display infos. Set contains the possible rotations - * for each supported device state. + * Returns, for the given displayId, a set of display infos. Set contains each supported device + * state. */ public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) { // Update display infos before returning, since any cached values would have been removed @@ -73,13 +68,13 @@ public class PossibleDisplayInfoMapper { } /** - * Updates the possible {@link DisplayInfo}s for the given display, by calculating the - * DisplayInfo for each rotation across supported device states. + * Updates the possible {@link DisplayInfo}s for the given display, by saving the DisplayInfo + * across supported device states. */ public void updatePossibleDisplayInfos(int displayId) { Set<DisplayInfo> displayInfos = mDisplayManagerInternal.getPossibleDisplayInfo(displayId); if (DEBUG) { - Slog.v(TAG, "updatePossibleDisplayInfos, calculate rotations for given DisplayInfo " + Slog.v(TAG, "updatePossibleDisplayInfos, given DisplayInfo " + displayInfos.size() + " on display " + displayId); } updateDisplayInfos(displayInfos); @@ -99,40 +94,12 @@ public class PossibleDisplayInfoMapper { private void updateDisplayInfos(Set<DisplayInfo> displayInfos) { // Empty out cache before re-computing. mDisplayInfos.clear(); - DisplayInfo[] originalDisplayInfos = new DisplayInfo[displayInfos.size()]; - displayInfos.toArray(originalDisplayInfos); // Iterate over each logical display layout for the current state. - Set<DisplayInfo> rotatedDisplayInfos; - for (DisplayInfo di : originalDisplayInfos) { - rotatedDisplayInfos = new ArraySet<>(); - // Calculate all possible rotations for each logical display. - for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { - rotatedDisplayInfos.add(applyRotation(di, rotation)); - } + for (DisplayInfo di : displayInfos) { // Combine all results under the logical display id. Set<DisplayInfo> priorDisplayInfos = mDisplayInfos.get(di.displayId, new ArraySet<>()); - priorDisplayInfos.addAll(rotatedDisplayInfos); + priorDisplayInfos.add(di); mDisplayInfos.put(di.displayId, priorDisplayInfos); } } - - private static DisplayInfo applyRotation(DisplayInfo displayInfo, - @Surface.Rotation int rotation) { - DisplayInfo updatedDisplayInfo = new DisplayInfo(); - updatedDisplayInfo.copyFrom(displayInfo); - // Apply rotations before updating width and height - updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation, - updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight); - updatedDisplayInfo.displayCutout = - DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached( - updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth, - updatedDisplayInfo.logicalHeight).getDisplayCutout(); - - updatedDisplayInfo.rotation = rotation; - final int naturalWidth = updatedDisplayInfo.getNaturalWidth(); - final int naturalHeight = updatedDisplayInfo.getNaturalHeight(); - updatedDisplayInfo.logicalWidth = naturalWidth; - updatedDisplayInfo.logicalHeight = naturalHeight; - return updatedDisplayInfo; - } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ca4c450a4592..c0dff14e5de5 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3382,7 +3382,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (record != null && record.isUid(uid) && Objects.equals(pkgName, record.packageName) && pPi.shouldShowNotificationDialogForTask(record.getTask().getTaskInfo(), - pkgName, record.intent)) { + pkgName, record.launchedFromPackage, record.intent, record.getName())) { validTaskId[0] = record.getTask().mTaskId; return true; } diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 813e06fecf48..ccd018faf075 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -26,6 +26,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN; +import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SNAPSHOT; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -114,7 +115,7 @@ public class StartingSurfaceController { if (allowTaskSnapshot) { parameter |= TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; } - if (activityCreated) { + if (activityCreated || startingWindowType == STARTING_WINDOW_TYPE_SNAPSHOT) { parameter |= TYPE_PARAMETER_ACTIVITY_CREATED; } if (isSolidColor) { @@ -138,7 +139,6 @@ public class StartingSurfaceController { final WindowState topFullscreenOpaqueWindow; final Task task; synchronized (mService.mGlobalLock) { - final WindowState mainWindow = activity.findMainWindow(); task = activity.getTask(); if (task == null) { Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity=" @@ -153,9 +153,9 @@ public class StartingSurfaceController { return null; } topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (mainWindow == null || topFullscreenOpaqueWindow == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for activity=" - + activity); + if (topFullscreenOpaqueWindow == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: no opaque window in " + + topFullscreenActivity); return null; } if (topFullscreenActivity.getWindowConfiguration().getRotation() diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f97f768872fd..00e61171cb68 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -56,6 +56,7 @@ import static android.view.SurfaceControl.METADATA_TASK_ID; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; @@ -1724,8 +1725,8 @@ class Task extends TaskFragment { /** Returns {@code true} if this task is currently in split-screen. */ boolean inSplitScreen() { return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW - && getRootTask() != null - && getRootTask().getAdjacentTaskFragment() != null; + && getCreatedByOrganizerTask() != null + && getCreatedByOrganizerTask().getAdjacentTaskFragment() != null; } private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) { @@ -3525,10 +3526,13 @@ class Task extends TaskFragment { mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY); info.startingWindowTypeParameter = activity.mStartingData.mTypeParams; - final WindowState mainWindow = activity.findMainWindow(); - if (mainWindow != null) { - info.mainWindowLayoutParams = mainWindow.getAttrs(); - info.requestedVisibilities.set(mainWindow.getRequestedVisibilities()); + if ((info.startingWindowTypeParameter + & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) { + final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION); + if (topMainWin != null) { + info.mainWindowLayoutParams = topMainWin.getAttrs(); + info.requestedVisibilities.set(topMainWin.getRequestedVisibilities()); + } } // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 73a755dd3123..1176182ede50 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -963,7 +963,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } else if (candidateTask != null) { final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; final Task launchRootTask = getLaunchRootTask(resolvedWindowingMode, activityType, - options, sourceTask, launchFlags); + options, sourceTask, launchFlags, candidateTask); if (launchRootTask != null) { if (candidateTask.getParent() == null) { launchRootTask.addChild(candidateTask, position); @@ -1117,6 +1117,13 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { @Nullable Task getLaunchRootTask(int windowingMode, int activityType, @Nullable ActivityOptions options, @Nullable Task sourceTask, int launchFlags) { + return getLaunchRootTask(windowingMode, activityType, options, sourceTask, launchFlags, + null /* candidateTask */); + } + + @Nullable + Task getLaunchRootTask(int windowingMode, int activityType, @Nullable ActivityOptions options, + @Nullable Task sourceTask, int launchFlags, @Nullable Task candidateTask) { // Try to use the launch root task in options if available. if (options != null) { final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); @@ -1157,9 +1164,19 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } // For a better split UX, If a task is launching from a created-by-organizer task, it should - // be launched into the same created-by-organizer task as well. - if (sourceTask != null) { - return sourceTask.getCreatedByOrganizerTask(); + // be launched into the same created-by-organizer task as well. Unless, the candidate task + // is already positioned in the split. + Task preferredRootInSplit = sourceTask != null && sourceTask.inSplitScreen() + ? sourceTask.getCreatedByOrganizerTask() : null; + if (preferredRootInSplit != null) { + if (candidateTask != null) { + final Task candidateRoot = candidateTask.getCreatedByOrganizerTask(); + if (candidateRoot != null && candidateRoot != preferredRootInSplit + && preferredRootInSplit == candidateRoot.getAdjacentTaskFragment()) { + preferredRootInSplit = candidateRoot; + } + } + return preferredRootInSplit; } return null; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 4cb4e9175534..56e96fa1fe58 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2156,7 +2156,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { return applicationType; } - return getTopChild().getActivityType(); + final ActivityRecord activity = getTopNonFinishingActivity(); + return activity != null ? activity.getActivityType() : getTopChild().getActivityType(); } @Override diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a9b0f0dc28ad..48168a08a543 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8755,8 +8755,7 @@ public class WindowManagerService extends IWindowManager.Stub return new ArrayList<>(); } - // Retrieve the DisplayInfo for all possible rotations across all possible display - // layouts. + // Retrieve the DisplayInfo across all possible display layouts. return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId)); } } finally { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c6288a7da26a..cd19f64ba12e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2464,7 +2464,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP dc.setImeLayeringTarget(null); dc.computeImeTarget(true /* updateImeTarget */); } - if (dc.getImeInputTarget() == this) { + if (dc.getImeInputTarget() == this + && (mActivityRecord == null || !mActivityRecord.isRelaunching())) { dc.updateImeInputAndControlTarget(null); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d1fac871fa2e..0a426eb80c54 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -13057,9 +13057,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (callerPackage == null) { return false; } + + CallerIdentity caller = new CallerIdentity(callerUid, null, null); if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid)) && (isActiveProfileOwner(callerUid) - || isActiveDeviceOwner(callerUid))) { + || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) { // device owner or a profile owner affiliated with the device owner return true; } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index bbc28d78d6f2..d3222901db1e 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -274,36 +274,11 @@ public final class ProfcollectForwardingService extends SystemService { } } - private class AppLaunchObserver implements ActivityMetricsLaunchObserver { + private class AppLaunchObserver extends ActivityMetricsLaunchObserver { @Override public void onIntentStarted(Intent intent, long timestampNanos) { traceOnAppStart(intent.getPackage()); } - - @Override - public void onIntentFailed() { - // Ignored - } - - @Override - public void onActivityLaunched(byte[] activity, int temperature) { - // Ignored - } - - @Override - public void onActivityLaunchCancelled(byte[] abortingActivity) { - // Ignored - } - - @Override - public void onActivityLaunchFinished(byte[] finalActivity, long timestampNanos) { - // Ignored - } - - @Override - public void onReportFullyDrawn(byte[] activity, long timestampNanos) { - // Ignored - } } private void registerOTAObserver() { 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 529def3697cd..8461b39f8899 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2758,7 +2758,7 @@ public class AlarmManagerServiceTest { mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); // No permission revoked. - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString(), + verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(), anyBoolean()); // Permission got granted only for (appId1, userId2). @@ -2813,14 +2813,14 @@ public class AlarmManagerServiceTest { mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); // Permission got revoked only for (appId1, userId2) - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); - verify(mService).removeExactAlarmsOnPermissionRevokedLocked( + verify(mService).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); } @@ -2833,7 +2833,7 @@ public class AlarmManagerServiceTest { mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); - verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, + verify(mService).removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, true); } @@ -2919,7 +2919,7 @@ public class AlarmManagerServiceTest { null); assertEquals(6, mService.mAlarmStore.size()); - mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, + mService.removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, true); final ArrayList<Alarm> remaining = mService.mAlarmStore.asList(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index c747a5fd982b..2f68306e9ba1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -191,6 +191,8 @@ public class MockingOomAdjusterTests { mock(ActivityManagerService.LocalService.class)); setFieldValue(ActivityManagerService.class, sService, "mBatteryStatsService", mock(BatteryStatsService.class)); + setFieldValue(ActivityManagerService.class, sService, "mInjector", + new ActivityManagerService.Injector(sContext)); doReturn(mock(AppOpsManager.class)).when(sService).getAppOpsManager(); doCallRealMethod().when(sService).enqueueOomAdjTargetLocked(any(ProcessRecord.class)); doCallRealMethod().when(sService).updateOomAdjPendingTargetsLocked(any(String.class)); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e3be3a792549..16df5deb2e5c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -19,9 +19,6 @@ android_test { "src/**/*.java", "src/**/*.kt", - "aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl", - "aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl", - "test-apps/JobTestApp/src/**/*.java", "test-apps/SuspendTestApp/src/**/*.java", @@ -67,10 +64,6 @@ android_test { "ActivityContext", ], - aidl: { - local_include_dirs: ["aidl"], - }, - libs: [ "android.hardware.power-V1-java", "android.hardware.tv.cec-V1.0-java", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index bb3eb81df6ed..158bd39a4fd0 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -28,7 +28,6 @@ <option name="install-arg" value="-t" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="JobTestApp.apk" /> - <option name="test-file-name" value="ConnTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> <option name="test-file-name" value="SimpleServiceTestApp2.apk" /> diff --git a/services/tests/servicestests/aidl/Android.bp b/services/tests/servicestests/aidl/Android.bp deleted file mode 100644 index 678053192e82..000000000000 --- a/services/tests/servicestests/aidl/Android.bp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2017 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 { - // 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"], -} - -java_library { - name: "servicestests-aidl", - sdk_version: "current", - srcs: [ - "com/android/servicestests/aidl/INetworkStateObserver.aidl", - "com/android/servicestests/aidl/ICmdReceiverService.aidl", - ], -} diff --git a/services/tests/servicestests/aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl b/services/tests/servicestests/aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl deleted file mode 100644 index d96450478f90..000000000000 --- a/services/tests/servicestests/aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2017 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.servicestests.aidl; - -interface ICmdReceiverService { - void finishActivity(); -}
\ No newline at end of file diff --git a/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl deleted file mode 100644 index ca9fc4c439d2..000000000000 --- a/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2017 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.servicestests.aidl; - -oneway interface INetworkStateObserver { - /** - * {@param resultData} will be in the format - * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo. - * For detailed info, see - * servicestests/test-apps/ConnTestApp/.../ConnTestActivity#checkNetworkStatus - */ - void onNetworkStateChecked(String resultData); -}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java index 5c91b8b4717f..d1390c68e130 100644 --- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java @@ -90,6 +90,16 @@ public class DropboxRateLimiterTest { mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); assertEquals(2, mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); + + // After 11 seconds the rate limiting buffer will be cleared and rate limiting will stop. + mClock.setOffsetMillis(11000); + + // The first call after rate limiting stops will still return the number of dropped events. + assertEquals(2, + mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); + // The next call should show that the dropped event counter was reset. + assertEquals(0, + mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); } private static class TestClock implements DropboxRateLimiter.Clock { diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java deleted file mode 100644 index 25b41db1aea3..000000000000 --- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) 2017 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.net; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.os.BatteryManager; -import android.os.Bundle; -import android.os.IBinder; -import android.os.SystemClock; -import android.support.test.uiautomator.UiDevice; -import android.text.TextUtils; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.servicestests.aidl.ICmdReceiverService; -import com.android.servicestests.aidl.INetworkStateObserver; - -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Tests for verifying network availability on activity start. - * - * To run the tests, use - * - * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services - * - * or the following steps: - * - * Build: m FrameworksServicesTests - * Install: adb install -r \ - * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \ - * com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner - */ -@LargeTest -@RunWith(AndroidJUnit4.class) -public class ConnOnActivityStartTest { - private static final String TAG = ConnOnActivityStartTest.class.getSimpleName(); - - private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp"; - private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity"; - private static final String TEST_SERVICE_CLASS = TEST_PKG + ".CmdReceiverService"; - - private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; - - private static final long BATTERY_OFF_TIMEOUT_MS = 2000; // 2 sec - private static final long BATTERY_OFF_CHECK_INTERVAL_MS = 200; // 0.2 sec - - private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec - - private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec - - private static final long BIND_SERVICE_TIMEOUT_SEC = 4; - - private static final int REPEAT_TEST_COUNT = 5; - - private static Context mContext; - private static UiDevice mUiDevice; - private static int mTestPkgUid; - private static BatteryManager mBatteryManager; - - private static ServiceConnection mServiceConnection; - private static ICmdReceiverService mCmdReceiverService; - - @BeforeClass - public static void setUpOnce() throws Exception { - mContext = InstrumentationRegistry.getContext(); - mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - - mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); - mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0); - - mBatteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE); - bindService(); - } - - @AfterClass - public static void tearDownOnce() throws Exception { - batteryReset(); - unbindService(); - } - - private static void bindService() throws Exception { - final CountDownLatch bindLatch = new CountDownLatch(1); - mServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Log.i(TAG, "Service connected"); - mCmdReceiverService = ICmdReceiverService.Stub.asInterface(service); - bindLatch.countDown(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.i(TAG, "Service disconnected"); - } - }; - final Intent intent = new Intent() - .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE_CLASS)); - // Needs to use BIND_ALLOW_OOM_MANAGEMENT and BIND_NOT_FOREGROUND so that the test app - // does not run in the same process state as this app. - mContext.bindService(intent, mServiceConnection, - Context.BIND_AUTO_CREATE - | Context.BIND_ALLOW_OOM_MANAGEMENT - | Context.BIND_NOT_FOREGROUND); - if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) { - fail("Timed out waiting for the service to bind in " + mTestPkgUid); - } - } - - private static void unbindService() { - if (mCmdReceiverService != null) { - mContext.unbindService(mServiceConnection); - } - } - - @Test - public void testStartActivity_batterySaver() throws Exception { - setBatterySaverMode(true); - try { - testConnOnActivityStart("testStartActivity_batterySaver"); - } finally { - setBatterySaverMode(false); - } - } - - @Test - public void testStartActivity_dataSaver() throws Exception { - setDataSaverMode(true); - try { - testConnOnActivityStart("testStartActivity_dataSaver"); - } finally { - setDataSaverMode(false); - } - } - - @Test - public void testStartActivity_dozeMode() throws Exception { - setDozeMode(true); - try { - testConnOnActivityStart("testStartActivity_dozeMode"); - } finally { - setDozeMode(false); - } - } - - @Test - public void testStartActivity_appStandby() throws Exception { - try{ - turnBatteryOn(); - setAppIdle(true); - turnScreenOn(); - startActivityAndCheckNetworkAccess(); - } finally { - turnBatteryOff(); - finishActivity(); - setAppIdle(false); - } - } - - @Test - public void testStartActivity_backgroundRestrict() throws Exception { - updateRestrictBackgroundBlacklist(true); - try { - testConnOnActivityStart("testStartActivity_backgroundRestrict"); - } finally { - updateRestrictBackgroundBlacklist(false); - } - } - - private void testConnOnActivityStart(String testName) throws Exception { - for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) { - try { - Log.d(TAG, testName + " Start #" + i); - turnScreenOn(); - startActivityAndCheckNetworkAccess(); - } finally { - finishActivity(); - Log.d(TAG, testName + " end #" + i); - } - } - } - - // TODO: Some of these methods are also used in CTS, so instead of duplicating code, - // create a static library which can be used by both servicestests and cts. - private void setBatterySaverMode(boolean enabled) throws Exception { - if (enabled) { - turnBatteryOn(); - executeCommand("settings put global low_power 1"); - } else { - executeCommand("settings put global low_power 0"); - turnBatteryOff(); - } - final String result = executeCommand("settings get global low_power"); - assertEquals(enabled ? "1" : "0", result); - } - - private void setDataSaverMode(boolean enabled) throws Exception { - executeCommand("cmd netpolicy set restrict-background " + enabled); - final String output = executeCommand("cmd netpolicy get restrict-background"); - final String expectedSuffix = enabled ? "enabled" : "disabled"; - assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'", - output.endsWith(expectedSuffix)); - } - - private void setDozeMode(boolean enabled) throws Exception { - if (enabled) { - turnBatteryOn(); - turnScreenOff(); - executeCommand("dumpsys deviceidle force-idle deep"); - } else { - turnScreenOn(); - turnBatteryOff(); - executeCommand("dumpsys deviceidle unforce"); - } - assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE", - 5 /* maxTries */, 500 /* napTimeMs */); - } - - private void setAppIdle(boolean enabled) throws Exception { - executeCommand("am set-inactive " + TEST_PKG + " " + enabled); - assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled, - 15 /* maxTries */, 2000 /* napTimeMs */); - } - - private void updateRestrictBackgroundBlacklist(boolean add) throws Exception { - if (add) { - executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid); - } else { - executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid); - } - assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add); - } - - private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception { - final int maxTries = 5; - boolean actual = false; - final String expectedUid = Integer.toString(uid); - String uids = ""; - for (int i = 1; i <= maxTries; i++) { - final String output = executeCommand("cmd netpolicy list " + list); - uids = output.split(":")[1]; - for (String candidate : uids.split(" ")) { - actual = candidate.trim().equals(expectedUid); - if (expected == actual) { - return; - } - } - Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected " - + expected + ", got " + actual + "); sleeping 1s before polling again"); - SystemClock.sleep(1000); - } - fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual - + ". Full list: " + uids); - } - - private void turnBatteryOn() throws Exception { - executeCommand("cmd battery unplug"); - executeCommand("cmd battery set status " + BatteryManager.BATTERY_STATUS_NOT_CHARGING); - assertBatteryOn(); - } - - private void assertBatteryOn() throws Exception { - final long endTime = SystemClock.uptimeMillis() + BATTERY_OFF_TIMEOUT_MS; - while (mBatteryManager.isCharging() && SystemClock.uptimeMillis() < endTime) { - SystemClock.sleep(BATTERY_OFF_CHECK_INTERVAL_MS); - } - assertFalse("Power should be disconnected", mBatteryManager.isCharging()); - } - - private void turnBatteryOff() throws Exception { - executeCommand("cmd battery set ac " + BatteryManager.BATTERY_PLUGGED_AC); - executeCommand("cmd battery set status " + BatteryManager.BATTERY_STATUS_CHARGING); - } - - private static void batteryReset() throws Exception { - executeCommand("cmd battery reset"); - } - - private void turnScreenOff() throws Exception { - executeCommand("input keyevent KEYCODE_SLEEP"); - } - - private void turnScreenOn() throws Exception { - executeCommand("input keyevent KEYCODE_WAKEUP"); - executeCommand("wm dismiss-keyguard"); - // Wait for screen-on state to propagate through the system. - SystemClock.sleep(SCREEN_ON_DELAY_MS); - } - - private static String executeCommand(String cmd) throws IOException { - final String result = executeSilentCommand(cmd); - Log.d(TAG, String.format("Result for '%s': %s", cmd, result)); - return result; - } - - private static String executeSilentCommand(String cmd) throws IOException { - return mUiDevice.executeShellCommand(cmd).trim(); - } - - private void assertDelayedCommandResult(String cmd, String expectedResult, - int maxTries, int napTimeMs) throws Exception { - String result = ""; - for (int i = 1; i <= maxTries; ++i) { - result = executeCommand(cmd); - if (expectedResult.equals(result)) { - return; - } - Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '" - + expectedResult + "' on attempt #" + i - + "; sleeping " + napTimeMs + "ms before trying again"); - SystemClock.sleep(napTimeMs); - } - fail("Command '" + cmd + "' did not return '" + expectedResult + "' after " - + maxTries + " attempts. Last result: '" + result + "'"); - } - - private void startActivityAndCheckNetworkAccess() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - final Intent launchIntent = new Intent().setComponent( - new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS)); - final Bundle extras = new Bundle(); - final String[] errors = new String[] {null}; - extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() { - @Override - public void onNetworkStateChecked(String resultData) { - errors[0] = resultData; - latch.countDown(); - } - }); - launchIntent.putExtras(extras) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(launchIntent); - if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - if (errors[0] != null) { - fail("Network not available for test app " + mTestPkgUid + ". " + errors[0]); - } - } else { - fail("Timed out waiting for network availability status from test app " + mTestPkgUid); - } - } - - private static void fail(String msg) throws Exception { - dumpOnFailure(); - Assert.fail(msg); - } - - private static void dumpOnFailure() throws Exception { - dump("network_management"); - dump("netpolicy"); - dumpUsageStats(); - } - - private static void dumpUsageStats() throws Exception { - final String output = executeSilentCommand("dumpsys usagestats"); - final StringBuilder sb = new StringBuilder(); - final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n'); - splitter.setString(output); - String str; - while (splitter.hasNext()) { - str = splitter.next(); - if (str.contains("package=") && !str.contains(TEST_PKG)) { - continue; - } - if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) { - continue; - } - sb.append(str).append('\n'); - } - dump("usagestats", sb.toString()); - } - - private static void dump(String service) throws Exception { - dump(service, executeSilentCommand("dumpsys " + service)); - } - - private static void dump(String service, String dump) throws Exception { - Log.d(TAG, ">>> Begin dump " + service); - Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, TAG, dump, null); - Log.d(TAG, "<<< End dump " + service); - } - - private void finishActivity() throws Exception { - mCmdReceiverService.finishActivity(); - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/Android.bp b/services/tests/servicestests/test-apps/ConnTestApp/Android.bp deleted file mode 100644 index 0731e2ca1f41..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/Android.bp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2017 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 { - // 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"], -} - -android_test_helper_app { - name: "ConnTestApp", - - test_suites: ["device-tests"], - - static_libs: ["servicestests-aidl"], - srcs: ["**/*.java"], - - platform_apis: true, - certificate: "platform", - dex_preopt: { - enabled: false, - }, - optimize: { - enabled: false, - }, -} diff --git a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml deleted file mode 100644 index 201cd05052ea..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.servicestests.apps.conntestapp"> - - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> - - <application> - <activity android:name=".ConnTestActivity" - android:exported="true" /> - <service android:name=".CmdReceiverService" - android:exported="true" /> - </application> - -</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS deleted file mode 100644 index aa87958f1d53..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/net/OWNERS diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/CmdReceiverService.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/CmdReceiverService.java deleted file mode 100644 index 6130f3a3fc76..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/CmdReceiverService.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2017 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.servicestests.apps.conntestapp; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -import com.android.servicestests.aidl.ICmdReceiverService; - -public class CmdReceiverService extends Service { - private ICmdReceiverService.Stub mBinder = new ICmdReceiverService.Stub() { - @Override - public void finishActivity() { - ConnTestActivity.finishSelf(); - } - }; - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java deleted file mode 100644 index f8d1d03cd7a6..000000000000 --- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2017 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.servicestests.apps.conntestapp; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.INetworkPolicyManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.INetworkManagementService; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.servicestests.aidl.INetworkStateObserver; - -public class ConnTestActivity extends Activity { - private static final String TAG = ConnTestActivity.class.getSimpleName(); - - private static final String TEST_PKG = ConnTestActivity.class.getPackage().getName(); - private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH"; - private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; - - private static final Object INSTANCE_LOCK = new Object(); - @GuardedBy("instanceLock") - private static Activity sInstance; - - private BroadcastReceiver finishCommandReceiver = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - synchronized (INSTANCE_LOCK) { - sInstance = this; - } - Log.i(TAG, "onCreate called"); - - notifyNetworkStateObserver(); - - finishCommandReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "finish command received"); - ConnTestActivity.this.finish(); - } - }; - registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY)); - } - - @Override - public void onResume() { - super.onResume(); - Log.i(TAG, "onResume called"); - } - - @Override - public void onStop() { - Log.i(TAG, "onStop called"); - if (finishCommandReceiver != null) { - unregisterReceiver(finishCommandReceiver); - finishCommandReceiver = null; - } - super.onStop(); - } - - @Override - public void finish() { - synchronized (INSTANCE_LOCK) { - sInstance = null; - } - Log.i(TAG, "finish called"); - super.finish(); - } - - public static void finishSelf() { - synchronized (INSTANCE_LOCK) { - if (sInstance != null) { - sInstance.finish(); - } - } - } - - private void notifyNetworkStateObserver() { - if (getIntent() == null) { - return; - } - - final Bundle extras = getIntent().getExtras(); - if (extras == null) { - return; - } - final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface( - extras.getBinder(EXTRA_NETWORK_STATE_OBSERVER)); - if (observer != null) { - AsyncTask.execute(() -> { - try { - observer.onNetworkStateChecked(checkNetworkStatus()); - } catch (RemoteException e) { - Log.e(TAG, "Error occured while notifying the observer: " + e); - } - }); - } - } - - /** - * Checks whether the network is restricted. - * - * @return null if network is not restricted, otherwise an error message. - */ - private String checkNetworkStatus() { - final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - final INetworkPolicyManager npms = INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - try { - final boolean restrictedByFwRules = nms.isNetworkRestricted(Process.myUid()); - final boolean restrictedByUidRules = npms.isUidNetworkingBlocked(Process.myUid(), true); - if (restrictedByFwRules || restrictedByUidRules) { - return "Network is restricted by fwRules: " + restrictedByFwRules - + " and uidRules: " + restrictedByUidRules; - } - return null; - } catch (RemoteException e) { - return "Error talking to system server: " + e; - } - } -}
\ No newline at end of file diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 767857bf2de8..e8e3a8f84f21 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -33,6 +33,7 @@ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index f4b9e258f7e0..76d4059eb436 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -30,8 +30,11 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.atLeast; 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.verify; import static org.mockito.Mockito.when; @@ -46,6 +49,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; import android.testing.TestableContext; import android.util.ArraySet; import android.util.Pair; @@ -59,11 +64,13 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.reflection.FieldSetter; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.List; public class NotificationListenersTest extends UiServiceTestCase { @@ -388,4 +395,66 @@ public class NotificationListenersTest extends UiServiceTestCase { verify(mContext).sendBroadcastAsUser( any(), eq(UserHandle.of(userId)), nullable(String.class)); } + + @Test + public void testNotifyPostedLockedInLockdownMode() { + NotificationRecord r = mock(NotificationRecord.class); + NotificationRecord old = mock(NotificationRecord.class); + + // before the lockdown mode + when(mNm.isInLockDownMode()).thenReturn(false); + mListeners.notifyPostedLocked(r, old, true); + mListeners.notifyPostedLocked(r, old, false); + verify(r, atLeast(2)).getSbn(); + + // in the lockdown mode + reset(r); + reset(old); + when(mNm.isInLockDownMode()).thenReturn(true); + mListeners.notifyPostedLocked(r, old, true); + mListeners.notifyPostedLocked(r, old, false); + verify(r, never()).getSbn(); + } + + @Test + public void testnotifyRankingUpdateLockedInLockdownMode() { + List chn = mock(List.class); + + // before the lockdown mode + when(mNm.isInLockDownMode()).thenReturn(false); + mListeners.notifyRankingUpdateLocked(chn); + verify(chn, atLeast(1)).size(); + + // in the lockdown mode + reset(chn); + when(mNm.isInLockDownMode()).thenReturn(true); + mListeners.notifyRankingUpdateLocked(chn); + verify(chn, never()).size(); + } + + @Test + public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { + NotificationRecord r = mock(NotificationRecord.class); + NotificationStats rs = mock(NotificationStats.class); + StatusBarNotification sbn = mock(StatusBarNotification.class); + FieldSetter.setField(mNm, + NotificationManagerService.class.getDeclaredField("mHandler"), + mock(NotificationManagerService.WorkerHandler.class)); + + // before the lockdown mode + when(mNm.isInLockDownMode()).thenReturn(false); + when(r.getSbn()).thenReturn(sbn); + mListeners.notifyRemovedLocked(r, 0, rs); + mListeners.notifyRemovedLocked(r, 0, rs); + verify(r, atLeast(2)).getSbn(); + + // in the lockdown mode + reset(r); + reset(rs); + when(mNm.isInLockDownMode()).thenReturn(true); + when(r.getSbn()).thenReturn(sbn); + mListeners.notifyRemovedLocked(r, 0, rs); + mListeners.notifyRemovedLocked(r, 0, rs); + verify(r, never()).getSbn(); + } } 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 b987c692bddb..348e015500fe 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -63,10 +63,13 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -352,6 +355,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); + TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker; private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @Mock @@ -508,6 +512,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setAudioManager(mAudioManager); + mStrongAuthTracker = mService.new StrongAuthTrackerFake(mContext); + mService.setStrongAuthTracker(mStrongAuthTracker); + mShortcutHelper = mService.getShortcutHelper(); mShortcutHelper.setLauncherApps(mLauncherApps); mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal); @@ -9247,4 +9254,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // make sure the summary was removed and not re-posted assertThat(mService.getNotificationRecordCount()).isEqualTo(0); } + + @Test + public void testStrongAuthTracker_isInLockDownMode() { + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + assertTrue(mStrongAuthTracker.isInLockDownMode()); + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + assertFalse(mStrongAuthTracker.isInLockDownMode()); + } + + @Test + public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() { + // post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // when entering the lockdown mode, cancel the 2 notifications. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + assertTrue(mStrongAuthTracker.isInLockDownMode()); + + // the notifyRemovedLocked function is called twice due to REASON_CANCEL_ALL. + ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); + verify(mListeners, times(2)).notifyRemovedLocked(any(), captor.capture(), any()); + assertEquals(REASON_CANCEL_ALL, captor.getValue().intValue()); + + // exit lockdown mode. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + + // the notifyPostedLocked function is called twice. + verify(mListeners, times(2)).notifyPostedLocked(any(), any()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java index bde048569e53..4ed7d35a097f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -113,4 +113,20 @@ public class TestableNotificationManagerService extends NotificationManagerServi protected void doChannelWarningToast(int uid, CharSequence toastText) { mChannelToastsSent.add(uid); } + + public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { + private int mGetStrongAuthForUserReturnValue = 0; + StrongAuthTrackerFake(Context context) { + super(context); + } + + public void setGetStrongAuthForUserReturnValue(int val) { + mGetStrongAuthForUserReturnValue = val; + } + + @Override + public int getStrongAuthForUser(int userId) { + return mGetStrongAuthForUserReturnValue; + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 7689e08bc3f3..2fea2284ff2a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -27,10 +27,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; @@ -40,24 +38,22 @@ import android.app.ActivityOptions; import android.app.ActivityOptions.SourceInfo; import android.app.WaitResult; import android.app.WindowConfiguration; +import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; +import android.util.Log; import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; -import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; - -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentCaptor; -import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.function.ToIntFunction; @@ -75,13 +71,14 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private ActivityMetricsLogger mActivityMetricsLogger; private ActivityMetricsLogger.LaunchingState mLaunchingState; private ActivityMetricsLaunchObserver mLaunchObserver; - private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry; private ActivityRecord mTrampolineActivity; private ActivityRecord mTopActivity; private ActivityOptions mActivityOptions; private boolean mLaunchTopByTrampoline; private boolean mNewActivityCreated = true; + private long mExpectedStartedId; + private final ArrayMap<ComponentName, Long> mLastLaunchedIds = new ArrayMap<>(); @Before public void setUpAMLO() { @@ -89,9 +86,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // ActivityTaskSupervisor always creates its own instance of ActivityMetricsLogger. mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger(); - - mLaunchObserverRegistry = mActivityMetricsLogger.getLaunchObserverRegistry(); - mLaunchObserverRegistry.registerLaunchObserver(mLaunchObserver); + mActivityMetricsLogger.getLaunchObserverRegistry().registerLaunchObserver(mLaunchObserver); // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful. // This seems to be the easiest way to create an ActivityRecord. @@ -107,65 +102,70 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mTrampolineActivity.mVisibleRequested = false; } - @After - public void tearDownAMLO() { - if (mLaunchObserverRegistry != null) { // Don't NPE if setUp failed. - mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver); - } + private <T> T verifyAsync(T mock) { + // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any + // messages that are waiting for the lock. + waitHandlerIdle(mAtm.mH); + // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. + return verify(mock, timeout(TIMEOUT_MS)); } - static class ActivityRecordMatcher implements ArgumentMatcher</*@ActivityRecordProto*/ byte[]> { - private final @ActivityRecordProto byte[] mExpected; - - public ActivityRecordMatcher(ActivityRecord activityRecord) { - mExpected = activityRecordToProto(activityRecord); - } + private void verifyOnActivityLaunched(ActivityRecord activity) { + final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class); + verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(), + eq(activity.mActivityComponent), anyInt()); + final long id = idCaptor.getValue(); + setExpectedStartedId(id, activity); + mLastLaunchedIds.put(activity.mActivityComponent, id); + } - public boolean matches(@ActivityRecordProto byte[] actual) { - return Arrays.equals(mExpected, actual); - } + private void verifyOnActivityLaunchFinished(ActivityRecord activity) { + verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId), + eq(activity.mActivityComponent), anyLong()); } - static @ActivityRecordProto byte[] activityRecordToProto(ActivityRecord record) { - return ActivityMetricsLogger.convertActivityRecordToProto(record); + private void setExpectedStartedId(long id, Object reason) { + mExpectedStartedId = id; + Log.i("AMLTest", "setExpectedStartedId=" + id + " from " + reason); } - static @ActivityRecordProto byte[] eqProto(ActivityRecord record) { - return argThat(new ActivityRecordMatcher(record)); + private void setLastExpectedStartedId(ActivityRecord r) { + setExpectedStartedId(getLastStartedId(r), r); } - private <T> T verifyAsync(T mock) { - // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any - // messages that are waiting for the lock. - waitHandlerIdle(mAtm.mH); - // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. - return verify(mock, timeout(TIMEOUT_MS)); + private long getLastStartedId(ActivityRecord r) { + final Long id = mLastLaunchedIds.get(r.mActivityComponent); + return id != null ? id : -1; } - private void verifyOnActivityLaunchFinished(ActivityRecord activity) { - verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(activity), anyLong()); + private long eqLastStartedId(ActivityRecord r) { + return eq(getLastStartedId(r)); } - private void onIntentStarted(Intent intent) { + private long onIntentStarted(Intent intent) { notifyActivityLaunching(intent); + long timestamp = -1; // If it is launching top activity from trampoline activity, the observer shouldn't receive // onActivityLaunched because the activities should belong to the same transition. if (!mLaunchTopByTrampoline) { - verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong()); + final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class); + verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), captor.capture()); + timestamp = captor.getValue(); } verifyNoMoreInteractions(mLaunchObserver); + return timestamp; } @Test public void testOnIntentFailed() { - onIntentStarted(new Intent("testOnIntentFailed")); + final long id = onIntentStarted(new Intent("testOnIntentFailed")); // Bringing an intent that's already running 'to front' is not considered // as an ACTIVITY_LAUNCHED state transition. notifyActivityLaunched(START_TASK_TO_FRONT, null /* launchedActivity */); - verifyAsync(mLaunchObserver).onIntentFailed(); + verifyAsync(mLaunchObserver).onIntentFailed(eq(id)); verifyNoMoreInteractions(mLaunchObserver); } @@ -208,9 +208,8 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private void onActivityLaunched(ActivityRecord activity) { onIntentStarted(activity.intent); - notifyActivityLaunched(START_SUCCESS, activity); + notifyAndVerifyActivityLaunched(activity); - verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(activity), anyInt()); verifyNoMoreInteractions(mLaunchObserver); } @@ -235,7 +234,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Cannot time already-visible activities. notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(mTopActivity)); verifyNoMoreInteractions(mLaunchObserver); } @@ -250,33 +249,33 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { .build(); notifyActivityLaunching(noDrawnActivity.intent); - notifyActivityLaunched(START_SUCCESS, noDrawnActivity); + notifyAndVerifyActivityLaunched(noDrawnActivity); noDrawnActivity.mVisibleRequested = false; mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity)); // If an activity is removed immediately before visibility update, it should cancel too. final ActivityRecord removedImm = new ActivityBuilder(mAtm).setCreateTask(true).build(); clearInvocations(mLaunchObserver); onActivityLaunched(removedImm); removedImm.removeImmediately(); - // Verify any() instead of proto because the field of record may be changed. - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(any()); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(removedImm)); } @Test public void testOnActivityLaunchWhileSleeping() { notifyActivityLaunching(mTrampolineActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTrampolineActivity); + notifyAndVerifyActivityLaunched(mTrampolineActivity); doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping(); mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test"); mTrampolineActivity.setVisibility(false); waitHandlerIdle(mAtm.mH); // Not cancel immediately because in one of real cases, the keyguard may be going away or // occluded later, then the activity can be drawn. - verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTrampolineActivity)); + verify(mLaunchObserver, never()).onActivityLaunchCancelled( + eqLastStartedId(mTrampolineActivity)); clearInvocations(mLaunchObserver); mLaunchTopByTrampoline = true; @@ -289,9 +288,8 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // The posted message will acquire wm lock, so the test needs to release the lock to verify. final Throwable error = awaitInWmLock(() -> { try { - // Though the aborting target should be eqProto(mTopActivity), use any() to avoid - // any changes in proto that may cause failure by different arguments. - verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(any()); + verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled( + mExpectedStartedId); } catch (Throwable e) { // Catch any errors including assertion because this runs in another thread. return e; @@ -314,9 +312,8 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mActivityOptions = ActivityOptions.makeBasic(); mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10); onIntentStarted(mTopActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTopActivity); - verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt()); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(prev)); + notifyAndVerifyActivityLaunched(mTopActivity); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eq(getLastStartedId(prev))); // The activity reports fully drawn before windows drawn, then the fully drawn event will // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}). @@ -328,7 +325,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { .isEqualTo(SourceInfo.TYPE_LAUNCHER); assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10); - verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong()); + verifyAsync(mLaunchObserver).onReportFullyDrawn(eq(mExpectedStartedId), anyLong()); verifyOnActivityLaunchFinished(mTopActivity); verifyNoMoreInteractions(mLaunchObserver); @@ -339,9 +336,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private void onActivityLaunchedTrampoline() { onIntentStarted(mTrampolineActivity.intent); - notifyActivityLaunched(START_SUCCESS, mTrampolineActivity); - - verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTrampolineActivity), anyInt()); + notifyAndVerifyActivityLaunched(mTrampolineActivity); // A second, distinct, activity launch is coalesced into the current app launch sequence. mLaunchTopByTrampoline = true; @@ -370,6 +365,11 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { mNewActivityCreated, activity, mActivityOptions); } + private void notifyAndVerifyActivityLaunched(ActivityRecord activity) { + notifyActivityLaunched(START_SUCCESS, activity); + verifyOnActivityLaunched(activity); + } + private void notifyTransitionStarting(ActivityRecord activity) { final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN); @@ -430,7 +430,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Cannot time already-visible activities. notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); - verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(mExpectedStartedId); verifyNoMoreInteractions(mLaunchObserver); } @@ -447,25 +447,14 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // be reported successfully. notifyTransitionStarting(mTopActivity); + verifyOnActivityLaunched(mTopActivity); verifyOnActivityLaunchFinished(mTopActivity); } @Test - public void testActivityRecordProtoIsNotTooBig() { - // The ActivityRecordProto must not be too big, otherwise converting it at runtime - // will become prohibitively expensive. - assertWithMessage("mTopActivity: %s", mTopActivity) - .that(activityRecordToProto(mTopActivity).length) - .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); - - assertWithMessage("mTrampolineActivity: %s", mTrampolineActivity) - .that(activityRecordToProto(mTrampolineActivity).length) - .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); - } - - @Test public void testConcurrentLaunches() { onActivityLaunched(mTopActivity); + clearInvocations(mLaunchObserver); final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState; final ActivityRecord otherActivity = new ActivityBuilder(mAtm) @@ -476,11 +465,13 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // state should be created here. onActivityLaunched(otherActivity); - assertWithMessage("Different callers should get 2 indepedent launching states") + assertWithMessage("Different callers should get 2 independent launching states") .that(previousState).isNotEqualTo(mLaunchingState); + setLastExpectedStartedId(otherActivity); transitToDrawnAndVerifyOnLaunchFinished(otherActivity); // The first transition should still be valid. + setLastExpectedStartedId(mTopActivity); transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); } @@ -534,10 +525,12 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Before TopActivity is drawn, it launches another activity on a different display. mActivityMetricsLogger.notifyActivityLaunching(activityOnNewDisplay.intent, mTopActivity /* caller */, mTopActivity.getUid()); - notifyActivityLaunched(START_SUCCESS, activityOnNewDisplay); + notifyAndVerifyActivityLaunched(activityOnNewDisplay); // There should be 2 events instead of coalescing as one event. + setLastExpectedStartedId(mTopActivity); transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); + setLastExpectedStartedId(activityOnNewDisplay); transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay); } @@ -548,9 +541,11 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { onActivityLaunched(mTrampolineActivity); mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent, mTrampolineActivity /* caller */, mTrampolineActivity.getUid()); - notifyActivityLaunched(START_SUCCESS, mTopActivity); + notifyAndVerifyActivityLaunched(mTopActivity); // Different windowing modes should be independent launch events. + setLastExpectedStartedId(mTrampolineActivity); transitToDrawnAndVerifyOnLaunchFinished(mTrampolineActivity); + setLastExpectedStartedId(mTopActivity); transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); } 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 d65e27d0f642..533540e2568d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -757,6 +757,8 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(activity.getTask()).build(); topActivity.setOccludesParent(false); + // The requested occluding state doesn't affect whether it fills parent. + assertTrue(topActivity.fillsParent()); activity.setState(STOPPED, "Testing"); activity.setVisibility(true); activity.makeActiveIfNeeded(null /* activeActivity */); @@ -1218,7 +1220,7 @@ public class ActivityRecordTests extends WindowTestsBase { task.setPausingActivity(currentTop); currentTop.finishing = true; currentTop.setState(PAUSED, "test"); - currentTop.completeFinishing("completePauseLocked"); + currentTop.completeFinishing(false /* updateVisibility */, "completePause"); // Current top becomes stopping because it is visible and the next is invisible. assertEquals(STOPPING, currentTop.getState()); @@ -3139,7 +3141,7 @@ public class ActivityRecordTests extends WindowTestsBase { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); InsetsSource imeSource = new InsetsSource(ITYPE_IME); - app.getInsetsState().addSource(imeSource); + app.mAboveInsetsState.addSource(imeSource); mDisplayContent.setImeLayeringTarget(app); mDisplayContent.updateImeInputAndControlTarget(app); @@ -3156,10 +3158,12 @@ public class ActivityRecordTests extends WindowTestsBase { // Simulate app re-start input or turning screen off/on then unlocked by un-secure // keyguard to back to the app, expect IME insets is not frozen mDisplayContent.updateImeInputAndControlTarget(app); + app.mActivityRecord.commitVisibility(true, false); assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + imeSource.setVisible(true); imeSource.setFrame(new Rect(100, 400, 500, 500)); - app.getInsetsState().addSource(imeSource); - app.getInsetsState().setSourceVisible(ITYPE_IME, true); + app.mAboveInsetsState.addSource(imeSource); // Verify when IME is visible and the app can receive the right IME insets from policy. makeWindowVisibleAndDrawn(app, mImeWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 33b70249dabe..8474a36dc681 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -46,7 +47,6 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -89,14 +89,6 @@ public class AppTransitionControllerTest extends WindowTestsBase { mAppTransitionController = new AppTransitionController(mWm, mDisplayContent); } - @Override - ActivityRecord createActivityRecord(DisplayContent dc, int windowingMode, int activityType) { - final ActivityRecord r = super.createActivityRecord(dc, windowingMode, activityType); - // Ensure that ActivityRecord#setOccludesParent takes effect. - doCallRealMethod().when(r).fillsParent(); - return r; - } - @Test public void testSkipOccludedActivityCloseTransition() { final ActivityRecord behind = createActivityRecord(mDisplayContent, @@ -135,7 +127,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentOpening.setOccludesParent(false); + doReturn(false).when(translucentOpening).fillsParent(); translucentOpening.setVisible(false); mDisplayContent.prepareAppTransition(TRANSIT_OPEN); mDisplayContent.mOpeningApps.add(behind); @@ -153,7 +145,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final ActivityRecord translucentClosing = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentClosing.setOccludesParent(false); + doReturn(false).when(translucentClosing).fillsParent(); mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); mDisplayContent.mClosingApps.add(translucentClosing); assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, @@ -562,6 +554,37 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testGetAnimationTargets_splitScreenOpening() { + // [DisplayContent] - [Task] -+- [split task 1] -+- [Task1] - [AR1] (opening, invisible) + // +- [split task 2] -+- [Task2] - [AR2] (opening, invisible) + final Task singleTopRoot = createTask(mDisplayContent); + final TaskBuilder builder = new TaskBuilder(mSupervisor) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setParentTaskFragment(singleTopRoot) + .setCreatedByOrganizer(true); + final Task splitRoot1 = builder.build(); + final Task splitRoot2 = builder.build(); + splitRoot1.setAdjacentTaskFragment(splitRoot2, false /* moveTogether */); + final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + opening.add(activity2); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + + // Promote animation targets up to Task level, not beyond. + assertEquals( + new ArraySet<>(new WindowContainer[]{splitRoot1, splitRoot2}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + } + + @Test public void testGetAnimationTargets_openingClosingTaskFragment() { // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible) // +- [TaskFragment2] - [ActivityRecord2] (closing, visible) diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 32d201fafcfb..263c9364c965 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -41,8 +41,10 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -1167,6 +1169,20 @@ public class DisplayContentTests extends WindowTestsBase { assertNull(mDisplayContent.computeImeParent()); } + @UseTestDisplay(addWindows = W_ACTIVITY) + @Test + public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception { + WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + overlay.setBounds(100, 100, 200, 200); + overlay.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM; + WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app"); + mDisplayContent.setImeLayeringTarget(overlay); + mDisplayContent.setImeInputTarget(app); + assertFalse(mDisplayContent.shouldImeAttachedToApp()); + assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(), + mDisplayContent.computeImeParent()); + } + @Test public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception { final DisplayContent dc = createNewDisplay(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 25cff61c3b78..e4eb98e79576 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -40,7 +40,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.clearInvocations; +import android.app.WindowConfiguration; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -515,6 +517,69 @@ public class DisplayRotationTests { } @Test + public void testAllowAllRotations_allowsUpsideDownSuggestion() + throws Exception { + mBuilder.build(); + mTarget.updateOrientation(SCREEN_ORIENTATION_UNSPECIFIED, true); + configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false); + when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations)) + .thenReturn(true); + freezeRotation(Surface.ROTATION_0); + enableOrientationSensor(); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180)); + assertTrue(waitForUiHandler()); + + verify(mMockStatusBarManagerInternal) + .onProposedRotationChanged(Surface.ROTATION_180, true); + } + + @Test + public void testDoNotAllowAllRotations_doesNotAllowUpsideDownSuggestion() + throws Exception { + mBuilder.build(); + mTarget.updateOrientation(SCREEN_ORIENTATION_UNSPECIFIED, true); + configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false); + when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations)) + .thenReturn(false); + freezeRotation(Surface.ROTATION_0); + enableOrientationSensor(); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180)); + assertTrue(waitForUiHandler()); + + verify(mMockStatusBarManagerInternal) + .onProposedRotationChanged(Surface.ROTATION_180, false); + } + + @Test + public void testAllowAllRotations_allowAllRotationsBecomesDisabled_forbidsUpsideDownSuggestion() + throws Exception { + mBuilder.build(); + mTarget.updateOrientation(SCREEN_ORIENTATION_UNSPECIFIED, true); + configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false); + when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations)) + .thenReturn(true); + freezeRotation(Surface.ROTATION_0); + enableOrientationSensor(); + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0)); + assertTrue(waitForUiHandler()); + + // Change resource to disallow all rotations. + // Reset "allowAllRotations". + mTarget.applyCurrentRotation(Surface.ROTATION_0); + clearInvocations(mMockStatusBarManagerInternal); + when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations)) + .thenReturn(false); + mTarget.resetAllowAllRotations(); + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180)); + assertTrue(waitForUiHandler()); + + verify(mMockStatusBarManagerInternal) + .onProposedRotationChanged(Surface.ROTATION_180, false); + } + + @Test public void testReturnsCompatibleRotation_SensorEnabled_RotationThawed() throws Exception { mBuilder.build(); configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false); @@ -721,14 +786,20 @@ public class DisplayRotationTests { doReturn(true).when(mMockDisplayPolicy).navigationBarCanMove(); doReturn(win).when(mMockDisplayPolicy).getTopFullscreenOpaqueWindow(); mMockDisplayContent.mCurrentFocus = win; - mTarget.mUpsideDownRotation = Surface.ROTATION_180; + // This should not affect the condition of shouldRotateSeamlessly. + mTarget.mUpsideDownRotation = Surface.ROTATION_90; doReturn(true).when(win.mActivityRecord).matchParentBounds(); // The focused fullscreen opaque window without override bounds should be able to be // rotated seamlessly. assertTrue(mTarget.shouldRotateSeamlessly( Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */)); + // Reject any 180 degree because non-movable navbar will be placed in a different position. + doReturn(false).when(mMockDisplayPolicy).navigationBarCanMove(); + assertFalse(mTarget.shouldRotateSeamlessly( + Surface.ROTATION_90, Surface.ROTATION_180, false /* forceUpdate */)); + doReturn(true).when(mMockDisplayPolicy).navigationBarCanMove(); doReturn(false).when(win.mActivityRecord).matchParentBounds(); // No seamless rotation if the window may be positioned with offset after rotation. assertFalse(mTarget.shouldRotateSeamlessly( @@ -935,6 +1006,8 @@ public class DisplayRotationTests { .thenReturn(WmDisplayCutout.NO_CUTOUT); when(mMockDisplayContent.getDefaultTaskDisplayArea()) .thenReturn(mock(TaskDisplayArea.class)); + when(mMockDisplayContent.getWindowConfiguration()) + .thenReturn(new WindowConfiguration()); mMockDisplayPolicy = mock(DisplayPolicy.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java index 6e0056821aab..8b0a54050c3c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRESENTATION; import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static com.google.common.truth.Truth.assertThat; @@ -90,8 +89,8 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); - // An entry for each possible rotation, for a display that can be in a single state. - assertThat(displayInfos.size()).isEqualTo(4); + // An entry for rotation 0, for a display that can be in a single state. + assertThat(displayInfos.size()).isEqualTo(1); assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); } @@ -100,7 +99,7 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mDefaultDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); - assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(1); // Add another display layout to the set of supported states. mPossibleDisplayInfo.add(mSecondDisplayInfo); @@ -116,12 +115,12 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { defaultDisplayInfos.add(di); } } - // An entry for each possible rotation, for the default display. - assertThat(defaultDisplayInfos).hasSize(4); + // An entry for rotation 0, for the default display. + assertThat(defaultDisplayInfos).hasSize(1); assertPossibleDisplayInfoEntries(defaultDisplayInfos, mDefaultDisplayInfo); - // An entry for each possible rotation, for the second display. - assertThat(secondDisplayInfos).hasSize(4); + // An entry for rotation 0, for the second display. + assertThat(secondDisplayInfos).hasSize(1); assertPossibleDisplayInfoEntries(secondDisplayInfos, mSecondDisplayInfo); } @@ -130,7 +129,7 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mDefaultDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); - assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(1); // Add another display to a different group. mSecondDisplayInfo.displayId = DEFAULT_DISPLAY + 1; @@ -139,14 +138,14 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId); Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); - // An entry for each possible rotation, for the default display. - assertThat(displayInfos).hasSize(4); + // An entry for rotation 0, for the default display. + assertThat(displayInfos).hasSize(1); assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); Set<DisplayInfo> secondStateEntries = mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId); - // An entry for each possible rotation, for the second display. - assertThat(secondStateEntries).hasSize(4); + // An entry for rotation 0, for the second display. + assertThat(secondStateEntries).hasSize(1); assertPossibleDisplayInfoEntries(secondStateEntries, mSecondDisplayInfo); } @@ -160,23 +159,10 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos, DisplayInfo expectedDisplayInfo) { - boolean[] seenEveryRotation = new boolean[4]; for (DisplayInfo displayInfo : displayInfos) { - final int rotation = displayInfo.rotation; - seenEveryRotation[rotation] = true; assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId); - assertEqualsRotatedDisplayInfo(displayInfo, expectedDisplayInfo); - } - assertThat(seenEveryRotation).isEqualTo(new boolean[]{true, true, true, true}); - } - - private static void assertEqualsRotatedDisplayInfo(DisplayInfo actual, DisplayInfo expected) { - if (actual.rotation == ROTATION_0 || actual.rotation == ROTATION_180) { - assertThat(actual.logicalWidth).isEqualTo(expected.logicalWidth); - assertThat(actual.logicalHeight).isEqualTo(expected.logicalHeight); - } else { - assertThat(actual.logicalWidth).isEqualTo(expected.logicalHeight); - assertThat(actual.logicalHeight).isEqualTo(expected.logicalWidth); + assertThat(displayInfo.logicalWidth).isEqualTo(expectedDisplayInfo.logicalWidth); + assertThat(displayInfo.logicalHeight).isEqualTo(expectedDisplayInfo.logicalHeight); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 80f6bceb884c..e5e0145095c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -741,4 +741,35 @@ public class TaskDisplayAreaTests extends WindowTestsBase { assertEquals(isAssistantOnTop ? topPosition : topPosition - 4, getTaskIndexOf(taskDisplayArea, assistRootTask)); } + + /** + * This test verifies proper launch root based on source and candidate task for split screen. + * If a task is launching from a created-by-organizer task, it should be launched into the + * same created-by-organizer task as well. Unless, the candidate task is already positioned in + * the split. + */ + @Test + public void getLaunchRootTaskInSplit() { + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + rootTask.mCreatedByOrganizer = true; + final Task adjacentRootTask = createTask( + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + adjacentRootTask.mCreatedByOrganizer = true; + final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/); + final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); + adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); + + // Verify the launch root with candidate task + Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */, + 0 /* launchFlags */, candidateTask); + assertSame(rootTask, actualRootTask.getRootTask()); + + // Verify the launch root task without candidate task + actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */, + 0 /* launchFlags */); + assertSame(adjacentRootTask, actualRootTask.getRootTask()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index c672b9173570..9957d05d0a52 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1156,10 +1156,6 @@ class WindowTestsBase extends SystemServiceTestsBase { spyOn(activity); if (mTask != null) { - // fullscreen value is normally read from resources in ctor, so for testing we need - // to set it somewhere else since we can't mock resources. - doReturn(true).when(activity).occludesParent(); - doReturn(true).when(activity).fillsParent(); mTask.addChild(activity); if (mOnTop) { // Move the task to front after activity is added. @@ -1294,6 +1290,7 @@ class WindowTestsBase extends SystemServiceTestsBase { private TaskFragment mParentTaskFragment; private boolean mCreateActivity = false; + private boolean mCreatedByOrganizer = false; TaskBuilder(ActivityTaskSupervisor supervisor) { mSupervisor = supervisor; @@ -1385,6 +1382,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return this; } + TaskBuilder setCreatedByOrganizer(boolean createdByOrganizer) { + mCreatedByOrganizer = createdByOrganizer; + return this; + } + Task build() { SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock); @@ -1420,7 +1422,8 @@ class WindowTestsBase extends SystemServiceTestsBase { .setActivityInfo(mActivityInfo) .setIntent(mIntent) .setOnTop(mOnTop) - .setVoiceSession(mVoiceSession); + .setVoiceSession(mVoiceSession) + .setCreatedByOrganizer(mCreatedByOrganizer); final Task task; if (mParentTaskFragment == null) { task = builder.setActivityType(mActivityType) diff --git a/telephony/java/Android.bp b/telephony/java/Android.bp index 3941b300206f..76a420c430d1 100644 --- a/telephony/java/Android.bp +++ b/telephony/java/Android.bp @@ -13,6 +13,15 @@ filegroup { srcs: [ "**/*.java", "**/*.aidl", + ":statslog-telephony-java-gen", ], visibility: ["//frameworks/base"], } + +genrule { + name: "statslog-telephony-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module telephony" + + " --javaPackage com.android.internal.telephony --javaClass TelephonyStatsLog", + out: ["com/android/internal/telephony/TelephonyStatsLog.java"], +} diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index ffdb23f98fb8..f47cf3384791 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -16,6 +16,8 @@ package android.telephony; +import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_ANOMALY_DETECTED; + import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.content.Context; @@ -24,6 +26,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.ParcelUuid; +import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.telephony.Rlog; @@ -83,6 +86,12 @@ public final class AnomalyReporter { return; } + TelephonyStatsLog.write( + TELEPHONY_ANOMALY_DETECTED, + 0, // TODO: carrier id needs to be populated + eventId.getLeastSignificantBits(), + eventId.getMostSignificantBits()); + // If this event has already occurred, skip sending intents for it; regardless log its // invocation here. Integer count = sEvents.containsKey(eventId) ? sEvents.get(eventId) + 1 : 1; |