diff options
318 files changed, 8176 insertions, 5829 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 18665e7f4d22..f49cdbf403f0 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -241,7 +241,7 @@ public class JobInfo implements Parcelable { /** * Default value for all regular jobs. As noted in {@link JobScheduler}, - * these jobs have a general maximum execution time of 10 minutes. + * these jobs have a general execution time of 10 minutes. * Receives the standard job management policy. */ public static final int PRIORITY_DEFAULT = 300; @@ -250,7 +250,7 @@ public class JobInfo implements Parcelable { * This task should be ordered ahead of most other tasks. It may be * deferred a little, but if it doesn't run at some point, the user may think * something is wrong. Assuming all constraints remain satisfied - * (including ideal system load conditions), these jobs will have a maximum + * (including ideal system load conditions), these jobs can have an * execution time of at least 4 minutes. Setting all of your jobs to high * priority will not be beneficial to your app and in fact may hurt its * performance in the long run. @@ -260,7 +260,7 @@ public class JobInfo implements Parcelable { /** * This task should be run ahead of all other tasks. Only Expedited Jobs * {@link Builder#setExpedited(boolean)} can have this priority and as such, - * are subject to the same maximum execution time details noted in + * are subject to the same execution time details noted in * {@link Builder#setExpedited(boolean)}. * A sample task of max priority: receiving a text message and processing it to * show a notification @@ -1414,6 +1414,15 @@ public class JobInfo implements Parcelable { * you also need to define the network traffic used by each work item * when constructing them. * + * <p class="note"> + * Prior to Android version {@link Build.VERSION_CODES#TIRAMISU}, JobScheduler used the + * estimated transfer numbers in a similar fashion to + * {@link #setMinimumNetworkChunkBytes(long)} (to estimate if the work would complete + * within the time available to job). In other words, JobScheduler treated the transfer as + * all-or-nothing. Starting from Android version {@link Build.VERSION_CODES#TIRAMISU}, + * JobScheduler will only use the estimated transfer numbers in this manner if minimum + * chunk sizes have not been provided via {@link #setMinimumNetworkChunkBytes(long)}. + * * @param downloadBytes The estimated size of network traffic that will * be downloaded by this job, in bytes. * @param uploadBytes The estimated size of network traffic that will be @@ -1756,14 +1765,19 @@ public class JobInfo implements Parcelable { * * <p> * Assuming all constraints remain satisfied (including ideal system load conditions), - * expedited jobs will have a maximum execution time of at least 1 minute. If your + * expedited jobs can have an execution time of at least 1 minute. If your * app has remaining expedited job quota, then the expedited job <i>may</i> potentially run * longer until remaining quota is used up. Just like with regular jobs, quota is not * consumed while the app is on top and visible to the user. * - * <p> + * <p class="note"> * Note: Even though expedited jobs are meant to run as soon as possible, they may be * deferred if the system is under heavy load or requested constraints are not satisfied. + * This delay may be true for expedited jobs of the foreground app on Android version + * {@link Build.VERSION_CODES#S}, but starting from Android version + * {@link Build.VERSION_CODES#TIRAMISU}, expedited jobs for the foreground app are + * guaranteed to be started before {@link JobScheduler#schedule(JobInfo)} returns (assuming + * all requested constraints are satisfied), similar to foreground services. * * @see JobInfo#isExpedited() */ @@ -1799,6 +1813,9 @@ public class JobInfo implements Parcelable { * and in the background, or the job failed due to unsatisfied constraints, * this job should be expected to behave like other jobs without this flag. * + * <p> + * Jobs marked as important-while-foreground are given {@link #PRIORITY_HIGH} by default. + * * @param importantWhileForeground whether to relax doze restrictions for this job when the * app is in the foreground. False by default. * @see JobInfo#isImportantWhileForeground() @@ -1831,8 +1848,9 @@ public class JobInfo implements Parcelable { * the specific user of this device. For example, fetching top headlines * of interest to the current user. * <p> - * Starting with Android version {@link Build.VERSION_CODES#TIRAMISU}, prefetch jobs are - * not allowed to have deadlines (set via {@link #setOverrideDeadline(long)}. + * Apps targeting Android version {@link Build.VERSION_CODES#TIRAMISU} or later are + * not allowed to have deadlines (set via {@link #setOverrideDeadline(long)} on their + * prefetch jobs. * <p> * The system may use this signal to relax the network constraints you * originally requested, such as allowing a diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index 1429c45ef2d2..ed72530d8608 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -167,7 +167,7 @@ public class JobParameters implements Parcelable { /** * The job used up its maximum execution time and timed out. Each individual job has a maximum * execution time limit, regardless of how much total quota the app has. See the note on - * {@link JobScheduler} for the execution time limits. + * {@link JobScheduler} and {@link JobInfo} for the execution time limits. */ public static final int STOP_REASON_TIMEOUT = 3; /** 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/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index c8ec89475050..c86353c84467 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -690,6 +690,12 @@ class JobConcurrencyManager { int projectedRunningCount = numRunningJobs; while ((nextPending = pendingJobQueue.next()) != null) { if (mRunningJobs.contains(nextPending)) { + // Should never happen. + Slog.wtf(TAG, "Pending queue contained a running job"); + if (DEBUG) { + Slog.e(TAG, "Pending+running job: " + nextPending); + } + pendingJobQueue.remove(nextPending); continue; } @@ -1137,7 +1143,8 @@ class JobConcurrencyManager { } } - if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT) { + final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); + if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) { worker.clearPreferredUid(); // We're over the limit (because the TOP app scheduled a lot of EJs). Don't start // running anything new until we get back below the limit. @@ -1145,7 +1152,6 @@ class JobConcurrencyManager { return; } - final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { updateCounterConfigLocked(); // Preemption case needs special care. @@ -1162,6 +1168,12 @@ class JobConcurrencyManager { pendingJobQueue.resetIterator(); while ((nextPending = pendingJobQueue.next()) != null) { if (mRunningJobs.contains(nextPending)) { + // Should never happen. + Slog.wtf(TAG, "Pending queue contained a running job"); + if (DEBUG) { + Slog.e(TAG, "Pending+running job: " + nextPending); + } + pendingJobQueue.remove(nextPending); continue; } @@ -1239,6 +1251,12 @@ class JobConcurrencyManager { while ((nextPending = pendingJobQueue.next()) != null) { if (mRunningJobs.contains(nextPending)) { + // Should never happen. + Slog.wtf(TAG, "Pending queue contained a running job"); + if (DEBUG) { + Slog.e(TAG, "Pending+running job: " + nextPending); + } + pendingJobQueue.remove(nextPending); continue; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 9e131339595f..358c32722b8b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1749,8 +1749,13 @@ public class JobSchedulerService extends com.android.server.SystemService if (!removed) { // We never create JobStatus objects for the express purpose of removing them, and this // method is only ever called for jobs that were saved in the JobStore at some point, - // so if we can't find it, something went seriously wrong. - Slog.wtfStack(TAG, "Job didn't exist in JobStore"); + // so if we can't find it, something may be wrong. As of Android T, there is a + // legitimate code path where removed is false --- when an actively running job is + // cancelled (eg. via JobScheduler.cancel() or the app scheduling a new job with the + // same job ID), we remove it from the JobStore and tell the JobServiceContext to stop + // running the job. Once the job stops running, we then call this method again. + // TODO: rework code so we don't intentionally call this method twice for the same job + Slog.w(TAG, "Job didn't exist in JobStore"); } if (mReadyToRock) { for (int i = 0; i < mControllers.size(); i++) { 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/api/api.go b/api/api.go index 9aac879b4eae..ce8cd1426661 100644 --- a/api/api.go +++ b/api/api.go @@ -208,12 +208,6 @@ func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) { props := libraryProps{} props.Name = proptools.StringPtr("all-framework-module-impl") props.Static_libs = transformArray(modules, "", ".impl") - // Media module's impl jar is called "updatable-media" - for i, v := range props.Static_libs { - if v == "framework-media.impl" { - props.Static_libs[i] = "updatable-media" - } - } props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(java.LibraryFactory, &props) diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt index 0c1ebb3b2208..27436ce35867 100644 --- a/core/api/module-lib-lint-baseline.txt +++ b/core/api/module-lib-lint-baseline.txt @@ -1,4 +1,6 @@ // Baseline format: 1.0 +SamShouldBeLast: android.app.Activity#convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions): + SAM-compatible parameters (such as parameter 1, "callback", in android.app.Activity.convertToTranslucent) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.ActivityManager#addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int): SAM-compatible parameters (such as parameter 1, "listener", in android.app.ActivityManager.addOnUidImportanceListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler): @@ -9,6 +11,24 @@ SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, an SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.PendingIntent#send(int, android.app.PendingIntent.OnFinished, android.os.Handler): SAM-compatible parameters (such as parameter 2, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String): + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ApplicationInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String): + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpBack) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String): + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler): + SAM-compatible parameters (such as parameter 1, "listener", in android.location.LocationManager.addNmeaListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioManager#abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes): SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.abandonAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int): diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 1b45e88584fe..025e8629fc20 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1,36 +1,20 @@ // Baseline format: 1.0 ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions(): - -ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler): - Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener` + Method should return Collection<CharSequence> (or subclass) instead of raw array; was `java.lang.CharSequence[]` GenericException: android.app.prediction.AppPredictor#finalize(): - + Methods must not throw generic exceptions (`java.lang.Throwable`) GenericException: android.hardware.location.ContextHubClient#finalize(): - + Methods must not throw generic exceptions (`java.lang.Throwable`) GenericException: android.service.autofill.augmented.FillWindow#finalize(): - - - -IntentBuilderName: android.app.search.SearchAction#getIntent(): - -IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent(): - Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent` + Methods must not throw generic exceptions (`java.lang.Throwable`) KotlinKeyword: android.app.Notification#when: - + Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords -MissingGetterMatchingBuilder: android.os.NewUserRequest.Builder#setAdmin(): - android.os.NewUserRequest does not declare a `getAdmin()` method matching method android.os.NewUserRequest.Builder.setAdmin() -MissingGetterMatchingBuilder: android.os.NewUserRequest.Builder#setEphemeral(): - android.os.NewUserRequest does not declare a `getEphemeral()` method matching method android.os.NewUserRequest.Builder.setEphemeral() -MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUid(int): - android.security.keystore.KeyGenParameterSpec does not declare a `getUid()` method matching method android.security.keystore.KeyGenParameterSpec.Builder.setUid(int) -MissingGetterMatchingBuilder: android.service.autofill.Dataset.Builder#setFieldInlinePresentation(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.service.autofill.InlinePresentation): - android.service.autofill.Dataset does not declare a `getFieldInlinePresentation()` method matching method android.service.autofill.Dataset.Builder.setFieldInlinePresentation(android.view.autofill.AutofillId,android.view.autofill.AutofillValue,java.util.regex.Pattern,android.service.autofill.InlinePresentation) MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean): android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean) MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): @@ -38,175 +22,135 @@ MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#set MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0: - + Missing nullability on parameter `intent` in method `onUnbind` MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #0: - + Missing nullability on parameter `inputId` in method `onEvent` MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1: - + Missing nullability on parameter `eventType` in method `onEvent` MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2: - + Missing nullability on parameter `eventArgs` in method `onEvent` MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0: - + Missing nullability on parameter `base` in method `attachBaseContext` MissingNullability: android.provider.ContactsContract.MetadataSync#CONTENT_URI: - + Missing nullability on field `CONTENT_URI` in class `class android.provider.ContactsContract.MetadataSync` MissingNullability: android.provider.ContactsContract.MetadataSync#METADATA_AUTHORITY_URI: - + Missing nullability on field `METADATA_AUTHORITY_URI` in class `class android.provider.ContactsContract.MetadataSync` MissingNullability: android.provider.ContactsContract.MetadataSyncState#CONTENT_URI: - + Missing nullability on field `CONTENT_URI` in class `class android.provider.ContactsContract.MetadataSyncState` MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #0: - + Missing nullability on parameter `context` in method `attachInfo` MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #1: - + Missing nullability on parameter `info` in method `attachInfo` MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0: - + Missing nullability on parameter `intent` in method `onUnbind` MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: - + Missing nullability on parameter `fd` in method `dump` MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1: - + Missing nullability on parameter `pw` in method `dump` MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2: - + Missing nullability on parameter `args` in method `dump` MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0: - + Missing nullability on parameter `base` in method `attachBaseContext` MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0: - -MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringDaily(java.time.ZonedDateTime) parameter #0: - -MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringMonthly(java.time.ZonedDateTime) parameter #0: - -MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringWeekly(java.time.ZonedDateTime) parameter #0: - + Missing nullability on parameter `intent` in method `onUnbind` MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0: - + Missing nullability on parameter `intent` in method `onUnbind` MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): - + Missing nullability on method `setServiceId` return MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0: - - - -NoSettingsProvider: android.provider.Settings.Secure#FAST_PAIR_SCAN_ENABLED: - New setting keys are not allowed (Field: FAST_PAIR_SCAN_ENABLED); use getters/setters in relevant manager class - - -OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent): - Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent` + Missing nullability on parameter `serviceId` in method `setServiceId` ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context): - + Protected methods not allowed; must be public: method android.printservice.recommendation.RecommendationService.attachBaseContext(android.content.Context)} ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): - + Protected methods not allowed; must be public: method android.service.contentcapture.ContentCaptureService.dump(java.io.FileDescriptor,java.io.PrintWriter,String[])} ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context): - - - -RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount(): - Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) -RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimmingAmount(): - Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) -RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimAmount(float): - Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) -RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimmingAmount(float): - Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) + Protected methods not allowed; must be public: method android.service.notification.NotificationAssistantService.attachBaseContext(android.content.Context)} SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 6, "callback", in android.accounts.AccountManager.addAccount) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean): - + SAM-compatible parameters (such as parameter 1, "listener", in android.accounts.AccountManager.addOnAccountsUpdatedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[]): - + SAM-compatible parameters (such as parameter 1, "listener", in android.accounts.AccountManager.addOnAccountsUpdatedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 4, "callback", in android.accounts.AccountManager.confirmCredentials) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.editProperties) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.finishSession) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.getAccountsByTypeAndFeatures) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 5, "callback", in android.accounts.AccountManager.getAuthToken) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 5, "callback", in android.accounts.AccountManager.getAuthToken) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 4, "callback", in android.accounts.AccountManager.getAuthToken) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 7, "callback", in android.accounts.AccountManager.getAuthTokenByFeatures) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.hasFeatures) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.isCredentialsUpdateSuggested) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler): - + SAM-compatible parameters (such as parameter 2, "callback", in android.accounts.AccountManager.removeAccount) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.removeAccount) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#renameAccount(android.accounts.Account, String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.accounts.AccountManager.renameAccount) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 6, "callback", in android.accounts.AccountManager.startAddAccountSession) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 5, "callback", in android.accounts.AccountManager.startUpdateCredentialsSession) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.accounts.AccountManager#updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): - + SAM-compatible parameters (such as parameter 5, "callback", in android.accounts.AccountManager.updateCredentials) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 4, "listener", in android.app.AlarmManager.set) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 4, "listener", in android.app.AlarmManager.setExact) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 5, "listener", in android.app.AlarmManager.setWindow) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.app.WallpaperInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler): - -SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback): - + SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.content.IntentFilter#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "du", in android.content.IntentFilter.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ApplicationInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpBack) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ResolveInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.Location#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.location.Location.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler): - -SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback): - -SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback): - -SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback): - + SAM-compatible parameters (such as parameter 1, "listener", in android.location.LocationManager.addNmeaListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper): - -SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener): - -SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener): - + SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper): - -SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener): - + SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper): - + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper): - + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioFocusRequest.Builder.setOnAudioFocusChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int): - + SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler): - -SamShouldBeLast: android.media.AudioRecord#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback): - -SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback): - + SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRecord.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRouting.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.media.MediaCodec#setOnFrameRenderedListener(android.media.MediaCodec.OnFrameRenderedListener, android.os.Handler): + SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaCodec.setOnFrameRenderedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.MediaPlayer#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler): SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.MediaPlayer#setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler): @@ -215,82 +159,65 @@ SamShouldBeLast: android.media.MediaPlayer#setOnDrmPreparedListener(android.medi SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnDrmPreparedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.MediaPlayer#setOnMediaTimeDiscontinuityListener(android.media.MediaPlayer.OnMediaTimeDiscontinuityListener, android.os.Handler): SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnMediaTimeDiscontinuityListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions -SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler): - SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.MediaPlayer#setOnSubtitleDataListener(android.media.MediaPlayer.OnSubtitleDataListener, android.os.Handler): SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnSubtitleDataListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler): - -SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback): - + SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaRecorder.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName): - + SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler): - -SamShouldBeLast: android.media.session.MediaSessionManager#registerCallback(java.util.concurrent.Executor, android.media.session.MediaSessionManager.Callback): - + SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.addOnSession2TokensChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle): - + SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity): - + SAM-compatible parameters (such as parameter 1, "callback", in android.nfc.NfcAdapter.setBeamPushUrisCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...): - + SAM-compatible parameters (such as parameter 1, "callback", in android.nfc.NfcAdapter.setNdefPushMessageCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...): - + SAM-compatible parameters (such as parameter 1, "callback", in android.nfc.NfcAdapter.setOnNdefPushCompleteCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String): - + SAM-compatible parameters (such as parameter 1, "owner", in android.os.Binder.attachInterface) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int): - + SAM-compatible parameters (such as parameter 1, "recipient", in android.os.Binder.linkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int): - + SAM-compatible parameters (such as parameter 1, "recipient", in android.os.Binder.unlinkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Handler#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.os.Handler.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Handler#postAtTime(Runnable, Object, long): - + SAM-compatible parameters (such as parameter 1, "r", in android.os.Handler.postAtTime) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Handler#postAtTime(Runnable, long): - + SAM-compatible parameters (such as parameter 1, "r", in android.os.Handler.postAtTime) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Handler#postDelayed(Runnable, Object, long): - + SAM-compatible parameters (such as parameter 1, "r", in android.os.Handler.postDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Handler#postDelayed(Runnable, long): - + SAM-compatible parameters (such as parameter 1, "r", in android.os.Handler.postDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.Handler#removeCallbacks(Runnable, Object): - + SAM-compatible parameters (such as parameter 1, "r", in android.os.Handler.removeCallbacks) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int): - + SAM-compatible parameters (such as parameter 1, "recipient", in android.os.IBinder.linkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int): - + SAM-compatible parameters (such as parameter 1, "recipient", in android.os.IBinder.unlinkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.RecoverySystem#verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File): - + SAM-compatible parameters (such as parameter 2, "listener", in android.os.RecoverySystem.verifyPackage) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], String, int, String): SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], android.net.Uri, String): SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.View#postDelayed(Runnable, long): - + SAM-compatible parameters (such as parameter 1, "action", in android.view.View.postDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long): - + SAM-compatible parameters (such as parameter 1, "action", in android.view.View.postOnAnimationDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long): - + SAM-compatible parameters (such as parameter 2, "what", in android.view.View.scheduleDrawable) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "listener", in android.view.Window.addOnFrameMetricsAvailableListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addAccessibilityStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler): - + SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addTouchExplorationStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams): - - - -ServiceName: android.content.Context#CLOUDSEARCH_SERVICE: - - -UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle): - Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle` -UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle): - -UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle): - Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle` + SAM-compatible parameters (such as parameter 2, "filePathCallback", in android.webkit.WebChromeClient.onShowFileChooser) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 01604e6becf0..0a906bee6fad 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1,920 +1,270 @@ // Baseline format: 1.0 AcronymName: android.app.NotificationChannel#isImportanceLockedByOEM(): - + Acronyms should not be capitalized in method names: was `isImportanceLockedByOEM`, should this be `isImportanceLockedByOem`? AcronymName: android.app.NotificationChannel#setImportanceLockedByOEM(boolean): - - - -ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION: - -ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: - -ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER: - -ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER: - -ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_ADDITIONAL_CALL_INFO: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CALL_RAT_TYPE: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CHILD_NUMBER: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CNA: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CNAP: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CODEC: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_DIALSTRING: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_DISPLAY_TEXT: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_EMERGENCY_CALL: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_IS_CALL_PULL: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_OI: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_OIR: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_REMOTE_URI: - -ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_USSD: - -ActionValue: android.telephony.ims.ImsReasonInfo#EXTRA_MSG_SERVICE_NOT_AUTHORIZED: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#ACTION_CLEANUP: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#ACTION_FILE_DESCRIPTOR_REQUEST: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_FD_COUNT: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_FINAL_URI: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_FREE_URI_LIST: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_PAUSED_LIST: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_PAUSED_URI_LIST: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_SERVICE_ID: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_TEMP_FILES_IN_USE: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_TEMP_FILE_ROOT: - -ActionValue: android.telephony.mbms.vendor.VendorUtils#EXTRA_TEMP_LIST: - - - -AllUpper: android.media.audiopolicy.AudioProductStrategy#sDefaultAttributes: - Constant field names must be named with only upper case characters: `android.media.audiopolicy.AudioProductStrategy#sDefaultAttributes`, should be `S_DEFAULT_ATTRIBUTES`? - - -ArrayReturn: android.app.UiAutomation#executeShellCommandRw(String): - -ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#KeyphraseSoundModel(java.util.UUID, java.util.UUID, byte[], android.hardware.soundtrigger.SoundTrigger.Keyphrase[]) parameter #3: - -ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#KeyphraseSoundModel(java.util.UUID, java.util.UUID, byte[], android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int) parameter #3: - -ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#getKeyphrases(): - -ArrayReturn: android.location.GnssMeasurementsEvent#GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]) parameter #1: - + Acronyms should not be capitalized in method names: was `setImportanceLockedByOEM`, should this be `setImportanceLockedByOem`? + + ArrayReturn: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #10: - + Method parameter should be Collection<Descriptor> (or subclass) instead of raw array; was `android.media.audiofx.AudioEffect.Descriptor[]` ArrayReturn: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #11: - -ArrayReturn: android.metrics.LogMaker#LogMaker(Object[]) parameter #0: - -ArrayReturn: android.metrics.LogMaker#deserialize(Object[]) parameter #0: - -ArrayReturn: android.metrics.LogMaker#serialize(): - -ArrayReturn: android.net.TestNetworkManager#createTunInterface(android.net.LinkAddress[]) parameter #0: - -ArrayReturn: android.os.HwBlob#wrapArray(boolean[]): - -ArrayReturn: android.os.HwBlob#wrapArray(byte[]): - -ArrayReturn: android.os.HwBlob#wrapArray(double[]): - -ArrayReturn: android.os.HwBlob#wrapArray(float[]): - -ArrayReturn: android.os.HwBlob#wrapArray(int[]): - -ArrayReturn: android.os.HwBlob#wrapArray(long[]): - -ArrayReturn: android.os.HwBlob#wrapArray(short[]): - -ArrayReturn: android.os.NativeHandle#NativeHandle(java.io.FileDescriptor[], int[], boolean) parameter #0: - -ArrayReturn: android.os.NativeHandle#getFileDescriptors(): - -ArrayReturn: android.security.keystore.AttestationUtils#attestDeviceIds(android.content.Context, int[], byte[]): - -ArrayReturn: android.telephony.ims.ImsUtListener#onUtConfigurationCallBarringQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - -ArrayReturn: android.telephony.ims.ImsUtListener#onUtConfigurationCallForwardQueried(int, android.telephony.ims.ImsCallForwardInfo[]) parameter #1: - -ArrayReturn: android.telephony.ims.ImsUtListener#onUtConfigurationCallWaitingQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - -ArrayReturn: android.telephony.ims.stub.ImsRegistrationImplBase#onSubscriberAssociatedUriChanged(android.net.Uri[]) parameter #0: - + Method parameter should be Collection<Descriptor> (or subclass) instead of raw array; was `android.media.audiofx.AudioEffect.Descriptor[]` ArrayReturn: android.view.Display#getSupportedWideColorGamut(): - + Method should return Collection<ColorSpace> (or subclass) instead of raw array; was `android.graphics.ColorSpace[]` ArrayReturn: android.view.FocusFinder#sort(android.view.View[], int, int, android.view.ViewGroup, boolean) parameter #0: - -ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions(): - + Method parameter should be Collection<View> (or subclass) instead of raw array; was `android.view.View[]` ArrayReturn: android.view.contentcapture.ViewNode.ViewStructureImpl#setAutofillOptions(CharSequence[]) parameter #0: - -ArrayReturn: android.view.inspector.InspectableProperty#enumMapping(): - -ArrayReturn: android.view.inspector.InspectableProperty#flagMapping(): - - - -AutoBoxing: android.os.HwBlob#wrapArray(byte[]): - -AutoBoxing: android.os.HwBlob#wrapArray(double[]): - -AutoBoxing: android.os.HwBlob#wrapArray(float[]): - -AutoBoxing: android.os.HwBlob#wrapArray(int[]): - -AutoBoxing: android.os.HwBlob#wrapArray(long[]): - -AutoBoxing: android.os.HwBlob#wrapArray(short[]): - -AutoBoxing: android.os.VintfObject#getTargetFrameworkCompatibilityMatrixVersion(): - - - -BannedThrow: android.app.ActivityTaskManager#removeStacksInWindowingModes(int[]): - -BannedThrow: android.app.ActivityTaskManager#removeStacksWithActivityTypes(int[]): - -BannedThrow: android.app.ActivityTaskManager#setTaskWindowingMode(int, int, boolean): - -BannedThrow: android.app.ActivityTaskManager#setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean): - -BannedThrow: android.media.audiofx.AudioEffect#getParameter(byte[], byte[]): - -BannedThrow: android.media.audiofx.AudioEffect#getParameter(int, byte[]): - -BannedThrow: android.media.audiofx.AudioEffect#getParameter(int, int[]): - -BannedThrow: android.media.audiofx.AudioEffect#getParameter(int, short[]): - -BannedThrow: android.media.audiofx.AudioEffect#getParameter(int[], short[]): - -BannedThrow: android.media.audiofx.AudioEffect#setParameter(byte[], byte[]): - -BannedThrow: android.media.audiofx.AudioEffect#setParameter(int, byte[]): - -BannedThrow: android.media.audiofx.AudioEffect#setParameter(int, int): - -BannedThrow: android.media.audiofx.AudioEffect#setParameter(int, short): - -BannedThrow: android.media.audiofx.AudioEffect#setParameter(int[], byte[]): - -BannedThrow: android.media.audiofx.AudioEffect#setParameter(int[], int[]): - -BannedThrow: android.media.audiopolicy.AudioMix.Builder#Builder(android.media.audiopolicy.AudioMixingRule): - -BannedThrow: android.media.audiopolicy.AudioMix.Builder#build(): - -BannedThrow: android.media.audiopolicy.AudioMix.Builder#setDevice(android.media.AudioDeviceInfo): - -BannedThrow: android.media.audiopolicy.AudioMix.Builder#setFormat(android.media.AudioFormat): - -BannedThrow: android.media.audiopolicy.AudioMix.Builder#setRouteFlags(int): - -BannedThrow: android.media.audiopolicy.AudioMixingRule.Builder#addMixRule(int, Object): - -BannedThrow: android.media.audiopolicy.AudioMixingRule.Builder#addRule(android.media.AudioAttributes, int): - -BannedThrow: android.media.audiopolicy.AudioMixingRule.Builder#excludeMixRule(int, Object): - -BannedThrow: android.media.audiopolicy.AudioMixingRule.Builder#excludeRule(android.media.AudioAttributes, int): - -BannedThrow: android.media.audiopolicy.AudioPolicy#createAudioRecordSink(android.media.audiopolicy.AudioMix): - -BannedThrow: android.media.audiopolicy.AudioPolicy#createAudioTrackSource(android.media.audiopolicy.AudioMix): - -BannedThrow: android.media.audiopolicy.AudioPolicy#setFocusDuckingBehavior(int): - -BannedThrow: android.media.audiopolicy.AudioPolicy.Builder#addMix(android.media.audiopolicy.AudioMix): - -BannedThrow: android.media.audiopolicy.AudioPolicy.Builder#setLooper(android.os.Looper): - -BannedThrow: android.os.HwBinder#getService(String, String): - -BannedThrow: android.os.HwBinder#getService(String, String, boolean): - -BannedThrow: android.os.Process#getThreadScheduler(int): - - - -BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#allowPrivilegedPlaybackCapture(boolean): - -BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#excludeMixRule(int, Object): - -BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#excludeRule(android.media.AudioAttributes, int): - -BuilderSetStyle: android.net.NetworkCapabilities.Builder#removeCapability(int): - -BuilderSetStyle: android.net.NetworkCapabilities.Builder#removeTransportType(int): - -BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateDnsslLifetime(long): - -BuilderSetStyle: android.net.metrics.RaEvent.Builder#updatePrefixPreferredLifetime(long): - -BuilderSetStyle: android.net.metrics.RaEvent.Builder#updatePrefixValidLifetime(long): - -BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRdnssLifetime(long): - -BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRouteInfoLifetime(long): - -BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRouterLifetime(long): - -BuilderSetStyle: android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc(): - -BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse(): - -BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#permitIncorrectContextUse(): - + Method parameter should be Collection<CharSequence> (or subclass) instead of raw array; was `java.lang.CharSequence[]` -CallbackInterface: android.app.prediction.AppPredictor.Callback: - -CallbackInterface: android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback: - -CallbackInterface: android.widget.Magnifier.Callback: - +AutoBoxing: android.os.VintfObject#getTargetFrameworkCompatibilityMatrixVersion(): + Must avoid boxed primitives (`java.lang.Long`) -CallbackMethodName: android.os.RemoteCallback: - +BuilderSetStyle: android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc(): + Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.ThreadPolicy.Builder.detectExplicitGc() +BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#permitIncorrectContextUse(): + Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.VmPolicy.Builder.permitIncorrectContextUse() ConcreteCollection: android.content.AutofillOptions#disabledActivities: - + Field type is concrete collection (`android.util.ArrayMap`); must be higher-level interface ConcreteCollection: android.content.AutofillOptions#whitelistedActivitiesForAugmentedAutofill: - + Field type is concrete collection (`android.util.ArraySet`); must be higher-level interface ConcreteCollection: android.content.ContentCaptureOptions#ContentCaptureOptions(int, int, int, int, int, android.util.ArraySet<android.content.ComponentName>) parameter #5: - + Parameter type is concrete collection (`android.util.ArraySet`); must be higher-level interface ConcreteCollection: android.content.ContentCaptureOptions#whitelistedComponents: - + Field type is concrete collection (`android.util.ArraySet`); must be higher-level interface ConcreteCollection: android.database.sqlite.SQLiteDebug.PagerStats#dbStats: - -ConcreteCollection: android.os.HwParcel#readBoolVector(): - -ConcreteCollection: android.os.HwParcel#readDoubleVector(): - -ConcreteCollection: android.os.HwParcel#readFloatVector(): - -ConcreteCollection: android.os.HwParcel#readInt16Vector(): - -ConcreteCollection: android.os.HwParcel#readInt32Vector(): - -ConcreteCollection: android.os.HwParcel#readInt64Vector(): - -ConcreteCollection: android.os.HwParcel#readInt8Vector(): - -ConcreteCollection: android.os.HwParcel#readNativeHandleVector(): - -ConcreteCollection: android.os.HwParcel#readStringVector(): - -ConcreteCollection: android.os.HwParcel#writeBoolVector(java.util.ArrayList<java.lang.Boolean>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeDoubleVector(java.util.ArrayList<java.lang.Double>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeFloatVector(java.util.ArrayList<java.lang.Float>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeInt16Vector(java.util.ArrayList<java.lang.Short>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeInt32Vector(java.util.ArrayList<java.lang.Integer>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeInt64Vector(java.util.ArrayList<java.lang.Long>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeInt8Vector(java.util.ArrayList<java.lang.Byte>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeNativeHandleVector(java.util.ArrayList<android.os.NativeHandle>) parameter #0: - -ConcreteCollection: android.os.HwParcel#writeStringVector(java.util.ArrayList<java.lang.String>) parameter #0: - + Field type is concrete collection (`java.util.ArrayList`); must be higher-level interface ConcreteCollection: android.service.autofill.CompositeUserData#getFieldClassificationAlgorithms(): - + Return type is concrete collection (`android.util.ArrayMap`); must be higher-level interface ConcreteCollection: android.service.autofill.CompositeUserData#getFieldClassificationArgs(): - + Return type is concrete collection (`android.util.ArrayMap`); must be higher-level interface ConcreteCollection: android.service.autofill.InternalTransformation#batchApply(android.service.autofill.ValueFinder, android.widget.RemoteViews, java.util.ArrayList<android.util.Pair<java.lang.Integer,android.service.autofill.InternalTransformation>>) parameter #2: - + Parameter type is concrete collection (`java.util.ArrayList`); must be higher-level interface ConcreteCollection: android.service.autofill.UserData#getFieldClassificationAlgorithms(): - -ConcreteCollection: android.telephony.ims.ImsConferenceState#mParticipants: - + Return type is concrete collection (`android.util.ArrayMap`); must be higher-level interface ContextFirst: android.os.VibrationEffect#get(android.net.Uri, android.content.Context) parameter #1: - - - -ContextNameSuffix: android.telephony.mbms.vendor.MbmsGroupCallServiceBase: - + Context is distinct, so it must be the first argument (method `get`) EndsWithImpl: android.view.contentcapture.ViewNode.ViewStructureImpl: - + Don't expose your implementation details: `ViewStructureImpl` ends with `Impl` Enum: android.view.inspector.InspectableProperty.ValueType: - - - -EqualsAndHashCode: android.app.prediction.AppPredictionContext#equals(Object): - -EqualsAndHashCode: android.app.prediction.AppTarget#equals(Object): - -EqualsAndHashCode: android.app.prediction.AppTargetEvent#equals(Object): - -EqualsAndHashCode: android.net.apf.ApfCapabilities#equals(Object): - -EqualsAndHashCode: android.net.metrics.ApfProgramEvent#equals(Object): - -EqualsAndHashCode: android.net.metrics.ApfStats#equals(Object): - -EqualsAndHashCode: android.net.metrics.DhcpClientEvent#equals(Object): - -EqualsAndHashCode: android.net.metrics.IpManagerEvent#equals(Object): - -EqualsAndHashCode: android.net.metrics.IpReachabilityEvent#equals(Object): - -EqualsAndHashCode: android.net.metrics.NetworkEvent#equals(Object): - -EqualsAndHashCode: android.net.metrics.RaEvent#equals(Object): - -EqualsAndHashCode: android.net.metrics.ValidationProbeEvent#equals(Object): - -EqualsAndHashCode: android.os.IncidentManager.PendingReport#equals(Object): - + Enums are discouraged in Android APIs + + EqualsAndHashCode: android.os.StrictMode.ViolationInfo#hashCode(): - + Must override both equals and hashCode; missing one in android.os.StrictMode.ViolationInfo -ExecutorRegistration: android.content.pm.PackageManager#addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener): - -ExecutorRegistration: android.hardware.camera2.CameraDevice#createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler): - ExecutorRegistration: android.media.audiofx.AudioEffect#setParameterListener(android.media.audiofx.AudioEffect.OnParameterChangeListener): - -ExecutorRegistration: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener): - -ExecutorRegistration: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener): - -ExecutorRegistration: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback): - -ExecutorRegistration: android.os.IncidentManager#cancelAuthorization(android.os.IncidentManager.AuthListener): - -ExecutorRegistration: android.os.IncidentManager#requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener): - -ExecutorRegistration: android.os.RemoteCallback#RemoteCallback(android.os.RemoteCallback.OnResultListener, android.os.Handler): - + Registration methods should have overload that accepts delivery Executor: `setParameterListener` ExecutorRegistration: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler): - + Registration methods should have overload that accepts delivery Executor: `countPermissionApps` ExecutorRegistration: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler): - + Registration methods should have overload that accepts delivery Executor: `getAppPermissions` ExecutorRegistration: android.service.watchdog.ExplicitHealthCheckService#setCallback(android.os.RemoteCallback): - -ExecutorRegistration: android.telephony.ims.stub.ImsCallSessionImplBase#setListener(android.telephony.ims.ImsCallSessionListener): - -ExecutorRegistration: android.telephony.ims.stub.ImsUtImplBase#setListener(android.telephony.ims.ImsUtListener): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsDownloadServiceBase#initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#startGroupCall(int, long, java.util.List<java.lang.Integer>, java.util.List<java.lang.Integer>, android.telephony.mbms.GroupCallCallback): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsStreamingServiceBase#initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int): - -ExecutorRegistration: android.telephony.mbms.vendor.MbmsStreamingServiceBase#startStreaming(int, String, android.telephony.mbms.StreamingServiceCallback): - + Registration methods should have overload that accepts delivery Executor: `setCallback` ExecutorRegistration: android.window.WindowOrganizer#applySyncTransaction(android.window.WindowContainerTransaction, android.window.WindowContainerTransactionCallback): - + Registration methods should have overload that accepts delivery Executor: `applySyncTransaction` ForbiddenSuperClass: android.app.AppDetailsActivity: - + AppDetailsActivity should not extend `Activity`. Activity subclasses are impossible to compose. Expose a composable API instead. -GenericException: android.app.prediction.AppPredictor#finalize(): - GenericException: android.service.autofill.CharSequenceTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int): - + Methods must not throw generic exceptions (`java.lang.Exception`) GenericException: android.service.autofill.DateTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int): - + Methods must not throw generic exceptions (`java.lang.Exception`) GenericException: android.service.autofill.ImageTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int): - -GenericException: android.service.autofill.augmented.FillWindow#finalize(): - - - -GetterOnBuilder: android.hardware.display.BrightnessConfiguration.Builder#getMaxCorrectionsByCategory(): - -GetterOnBuilder: android.hardware.display.BrightnessConfiguration.Builder#getMaxCorrectionsByPackageName(): - + Methods must not throw generic exceptions (`java.lang.Exception`) -GetterSetterNames: android.app.NotificationChannel#isBlockableSystem(): - -GetterSetterNames: android.app.NotificationChannel#isImportanceLockedByCriticalDeviceFunction(): - -GetterSetterNames: android.app.NotificationChannel#isImportanceLockedByOEM(): - GetterSetterNames: android.location.GnssClock#setBiasNanos(double): - + Symmetric method for `hasBiasNanos` must be named `setHasBiasNanos`; was `setBiasNanos` GetterSetterNames: android.location.GnssClock#setBiasUncertaintyNanos(double): - + Symmetric method for `hasBiasUncertaintyNanos` must be named `setHasBiasUncertaintyNanos`; was `setBiasUncertaintyNanos` GetterSetterNames: android.location.GnssClock#setDriftNanosPerSecond(double): - + Symmetric method for `hasDriftNanosPerSecond` must be named `setHasDriftNanosPerSecond`; was `setDriftNanosPerSecond` GetterSetterNames: android.location.GnssClock#setDriftUncertaintyNanosPerSecond(double): - + Symmetric method for `hasDriftUncertaintyNanosPerSecond` must be named `setHasDriftUncertaintyNanosPerSecond`; was `setDriftUncertaintyNanosPerSecond` GetterSetterNames: android.location.GnssClock#setElapsedRealtimeNanos(long): - + Symmetric method for `hasElapsedRealtimeNanos` must be named `setHasElapsedRealtimeNanos`; was `setElapsedRealtimeNanos` GetterSetterNames: android.location.GnssClock#setElapsedRealtimeUncertaintyNanos(double): - + Symmetric method for `hasElapsedRealtimeUncertaintyNanos` must be named `setHasElapsedRealtimeUncertaintyNanos`; was `setElapsedRealtimeUncertaintyNanos` GetterSetterNames: android.location.GnssClock#setFullBiasNanos(long): - + Symmetric method for `hasFullBiasNanos` must be named `setHasFullBiasNanos`; was `setFullBiasNanos` GetterSetterNames: android.location.GnssClock#setLeapSecond(int): - + Symmetric method for `hasLeapSecond` must be named `setHasLeapSecond`; was `setLeapSecond` GetterSetterNames: android.location.GnssClock#setReferenceCarrierFrequencyHzForIsb(double): - + Symmetric method for `hasReferenceCarrierFrequencyHzForIsb` must be named `setHasReferenceCarrierFrequencyHzForIsb`; was `setReferenceCarrierFrequencyHzForIsb` GetterSetterNames: android.location.GnssClock#setReferenceCodeTypeForIsb(String): - + Symmetric method for `hasReferenceCodeTypeForIsb` must be named `setHasReferenceCodeTypeForIsb`; was `setReferenceCodeTypeForIsb` GetterSetterNames: android.location.GnssClock#setReferenceConstellationTypeForIsb(int): - + Symmetric method for `hasReferenceConstellationTypeForIsb` must be named `setHasReferenceConstellationTypeForIsb`; was `setReferenceConstellationTypeForIsb` GetterSetterNames: android.location.GnssClock#setTimeUncertaintyNanos(double): - + Symmetric method for `hasTimeUncertaintyNanos` must be named `setHasTimeUncertaintyNanos`; was `setTimeUncertaintyNanos` GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double): - + Symmetric method for `hasBasebandCn0DbHz` must be named `setHasBasebandCn0DbHz`; was `setBasebandCn0DbHz` GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float): - + Symmetric method for `hasCarrierFrequencyHz` must be named `setHasCarrierFrequencyHz`; was `setCarrierFrequencyHz` GetterSetterNames: android.location.GnssMeasurement#setCodeType(String): - + Symmetric method for `hasCodeType` must be named `setHasCodeType`; was `setCodeType` GetterSetterNames: android.location.GnssMeasurement#setCorrelationVectors(java.util.Collection<android.location.CorrelationVector>): - + Symmetric method for `hasCorrelationVectors` must be named `setHasCorrelationVectors`; was `setCorrelationVectors` GetterSetterNames: android.location.GnssMeasurement#setFullInterSignalBiasNanos(double): - + Symmetric method for `hasFullInterSignalBiasNanos` must be named `setHasFullInterSignalBiasNanos`; was `setFullInterSignalBiasNanos` GetterSetterNames: android.location.GnssMeasurement#setFullInterSignalBiasUncertaintyNanos(double): - + Symmetric method for `hasFullInterSignalBiasUncertaintyNanos` must be named `setHasFullInterSignalBiasUncertaintyNanos`; was `setFullInterSignalBiasUncertaintyNanos` GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasNanos(double): - + Symmetric method for `hasSatelliteInterSignalBiasNanos` must be named `setHasSatelliteInterSignalBiasNanos`; was `setSatelliteInterSignalBiasNanos` GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasUncertaintyNanos(double): - + Symmetric method for `hasSatelliteInterSignalBiasUncertaintyNanos` must be named `setHasSatelliteInterSignalBiasUncertaintyNanos`; was `setSatelliteInterSignalBiasUncertaintyNanos` GetterSetterNames: android.location.GnssMeasurement#setSatellitePvt(android.location.SatellitePvt): - + Symmetric method for `hasSatellitePvt` must be named `setHasSatellitePvt`; was `setSatellitePvt` GetterSetterNames: android.location.GnssMeasurement#setSnrInDb(double): - -GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored(): - -GetterSetterNames: android.location.LocationRequest#isLowPowerMode(): - + Symmetric method for `hasSnrInDb` must be named `setHasSnrInDb`; was `setSnrInDb` GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground(): Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground` -GetterSetterNames: android.os.IncidentReportArgs#isAll(): - -GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied(): - -GetterSetterNames: android.service.notification.NotificationStats#setExpanded(): - -GetterSetterNames: android.service.notification.NotificationStats#setSeen(): - -GetterSetterNames: android.service.notification.NotificationStats#setSnoozed(): - -GetterSetterNames: android.service.notification.NotificationStats#setViewedSettings(): - -GetterSetterNames: android.view.View#isAutofilled(): - -GetterSetterNames: android.view.View#isDefaultFocusHighlightEnabled(): - - - -IllegalStateException: android.media.audiopolicy.AudioMix.Builder#build(): - - - -IntentBuilderName: android.app.backup.BackupManager#getConfigurationIntent(String): - -IntentBuilderName: android.app.backup.BackupManager#getDataManagementIntent(String): - + + IntentBuilderName: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale): - + Methods creating an Intent should be named `create<Foo>Intent()`, was `getManageKeyphraseIntent` IntentName: android.provider.Settings.Secure#VOICE_INTERACTION_SERVICE: - + Intent action constant name must be ACTION_FOO: VOICE_INTERACTION_SERVICE IntentName: android.provider.Telephony.Sms.Intents#SMS_CARRIER_PROVISION_ACTION: - -IntentName: android.service.notification.Adjustment#KEY_CONTEXTUAL_ACTIONS: - + Intent action constant name must be ACTION_FOO: SMS_CARRIER_PROVISION_ACTION -InterfaceConstant: android.service.autofill.AutofillFieldClassificationService#SERVICE_INTERFACE: - -InterfaceConstant: android.service.autofill.augmented.AugmentedAutofillService#SERVICE_INTERFACE: - -InterfaceConstant: android.service.contentcapture.ContentCaptureService#SERVICE_INTERFACE: - -InterfaceConstant: android.service.notification.NotificationAssistantService#SERVICE_INTERFACE: - -InterfaceConstant: android.telecom.PhoneAccountSuggestionService#SERVICE_INTERFACE: - - - -InternalField: android.media.audiopolicy.AudioProductStrategy#sDefaultAttributes: - Internal field sDefaultAttributes must not be exposed -InternalField: android.telephony.ims.ImsConferenceState#mParticipants: - +KotlinOperator: android.os.PackageTagsList#contains(android.os.PackageTagsList): + Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object) +KotlinOperator: android.util.SparseArrayMap#get(int, K): + Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object) -KotlinOperator: android.os.WorkSource#get(int): - -KotlinOperator: android.util.SparseArrayMap#get(int, K): - -KotlinOperator: android.util.SparseArrayMap#get(int, String): - - - -ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener: - -ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener: - -ListenerInterface: android.os.IncidentManager.AuthListener: - -ListenerInterface: android.telephony.ims.ImsCallSessionListener: - -ListenerInterface: android.telephony.ims.ImsUtListener: - - - -ListenerLast: android.hardware.camera2.CameraDevice#createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) parameter #4: - -ListenerLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper) parameter #2: - ListenerLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler) parameter #3: - + Listeners should always be at end of argument list (method `countPermissionApps`) ListenerLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler) parameter #2: - -ListenerLast: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) parameter #1: - -ListenerLast: android.telephony.mbms.vendor.MbmsStreamingServiceBase#initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) parameter #1: - + Listeners should always be at end of argument list (method `getAppPermissions`) ManagerConstructor: android.content.pm.ShortcutManager#ShortcutManager(android.content.Context): - - - -ManagerLookup: android.telephony.ims.ImsMmTelManager#createForSubscriptionId(int): - -ManagerLookup: android.telephony.ims.ProvisioningManager#createForSubscriptionId(int): - - - -MethodNameTense: android.telephony.ims.feature.CapabilityChangeRequest#getCapabilitiesToEnable(): - - - -MethodNameUnits: android.telephony.ims.ImsCallForwardInfo#getTimeSeconds(): - + Managers must always be obtained from Context; no direct constructors MinMaxConstant: android.os.UserHandle#MIN_SECONDARY_USER_ID: - + If min/max could change in future, make them dynamic methods: android.os.UserHandle#MIN_SECONDARY_USER_ID MinMaxConstant: android.view.autofill.AutofillManager#MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS: - - - -MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setFlags(int): - -MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setOpNames(java.util.List<java.lang.String>): - -MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setPackageName(String): - -MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setUid(int): - -MissingGetterMatchingBuilder: android.content.integrity.RuleSet.Builder#addRules(java.util.List<android.content.integrity.Rule>): - -MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#addCorrectionByCategory(int, android.hardware.display.BrightnessCorrection): - -MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#addCorrectionByPackageName(String, android.hardware.display.BrightnessCorrection): - -MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#setDescription(String): - -MissingGetterMatchingBuilder: android.hardware.lights.LightsRequest.Builder#setLight(android.hardware.lights.Light, android.hardware.lights.LightState): - + If min/max could change in future, make them dynamic methods: android.view.autofill.AutofillManager#MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS + + MissingGetterMatchingBuilder: android.media.VolumeShaper.Configuration.Builder#setOptionFlags(int): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setDevice(android.media.AudioDeviceInfo): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setFormat(android.media.AudioFormat): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setRouteFlags(int): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMixingRule.Builder#addMixRule(int, Object): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMixingRule.Builder#addRule(android.media.AudioAttributes, int): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#addMix(android.media.audiopolicy.AudioMix): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setIsAudioFocusPolicy(boolean): - + android.media.VolumeShaper.Configuration does not declare a `getOptionFlags()` method matching method android.media.VolumeShaper.Configuration.Builder.setOptionFlags(int) MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setIsTestFocusPolicy(boolean): - -MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setLooper(android.os.Looper): - -MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setBytesRemaining(long): - -MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setExpiryTime(long): - -MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setRefreshTime(long): - -MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#addCapability(int): - -MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#setRequestorPackageName(String): - -MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#setRequestorUid(int): - -MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setShouldShowEntitlementUi(boolean): - -MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setStaticIpv4Addresses(android.net.LinkAddress, android.net.LinkAddress): - -MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setActualLifetime(long): - -MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setCurrentRas(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setFilteredRas(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setFlags(boolean, boolean): - -MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setLifetime(long): - -MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setProgramLength(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setDroppedRas(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setDurationMs(long): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setMatchingRas(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setMaxProgramSize(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setParseErrors(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdates(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdatesAll(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdatesAllowingMulticast(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setReceivedRas(int): - -MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setZeroLifetimeRas(int): - -MissingGetterMatchingBuilder: android.net.metrics.DhcpClientEvent.Builder#setDurationMs(int): - -MissingGetterMatchingBuilder: android.net.metrics.DhcpClientEvent.Builder#setMsg(String): - -MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setDurationMs(long): - -MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setProbeType(int, boolean): - -MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setReturnCode(int): - + android.media.audiopolicy.AudioPolicy does not declare a `isIsTestFocusPolicy()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setIsTestFocusPolicy(boolean) MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUniqueIdIncluded(boolean): - -MissingGetterMatchingBuilder: android.service.autofill.Dataset.Builder#setFieldInlinePresentation(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.service.autofill.InlinePresentation): - -MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setClientState(android.os.Bundle): - -MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setFillWindow(android.service.autofill.augmented.FillWindow): - -MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setInlineSuggestions(java.util.List<android.service.autofill.Dataset>): - -MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean): - + android.security.keystore.KeyGenParameterSpec does not declare a `isUniqueIdIncluded()` method matching method android.security.keystore.KeyGenParameterSpec.Builder.setUniqueIdIncluded(boolean) MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setIsAdhocConferenceCall(boolean): - + android.telecom.ConnectionRequest does not declare a `isIsAdhocConferenceCall()` method matching method android.telecom.ConnectionRequest.Builder.setIsAdhocConferenceCall(boolean) MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setRttPipeFromInCall(android.os.ParcelFileDescriptor): - + android.telecom.ConnectionRequest does not declare a `getRttPipeFromInCall()` method matching method android.telecom.ConnectionRequest.Builder.setRttPipeFromInCall(android.os.ParcelFileDescriptor) MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setRttPipeToInCall(android.os.ParcelFileDescriptor): - + android.telecom.ConnectionRequest does not declare a `getRttPipeToInCall()` method matching method android.telecom.ConnectionRequest.Builder.setRttPipeToInCall(android.os.ParcelFileDescriptor) MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setShouldShowIncomingCallUi(boolean): - -MissingGetterMatchingBuilder: android.telecom.PhoneAccount.Builder#setGroupId(String): - -MissingGetterMatchingBuilder: android.telephony.NetworkRegistrationInfo.Builder#setEmergencyOnly(boolean): - -MissingGetterMatchingBuilder: android.telephony.ims.ImsSsData.Builder#setCallForwardingInfo(java.util.List<android.telephony.ims.ImsCallForwardInfo>): - -MissingGetterMatchingBuilder: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#addFeature(int, int): - -MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): - + android.telecom.ConnectionRequest does not declare a `shouldShowIncomingCallUi()` method matching method android.telecom.ConnectionRequest.Builder.setShouldShowIncomingCallUi(boolean) MissingGetterMatchingBuilder: android.view.Display.Mode.Builder#setResolution(int, int): android.view.Display.Mode does not declare a `getResolution()` method matching method android.view.Display.Mode.Builder.setResolution(int,int) MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1: - -MissingNullability: android.app.ActivityManager#addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int) parameter #0: - + Missing nullability on parameter `config` in method `onMovedToDisplay` MissingNullability: android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName) parameter #0: - -MissingNullability: android.app.ActivityManager#forceStopPackage(String) parameter #0: - -MissingNullability: android.app.ActivityManager#getPackageImportance(String) parameter #0: - + Missing nullability on parameter `activity` in method `alwaysShowUnsupportedCompileSdkWarning` MissingNullability: android.app.ActivityManager#holdLock(android.os.IBinder, int) parameter #0: - -MissingNullability: android.app.ActivityManager#removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener) parameter #0: - + Missing nullability on parameter `token` in method `holdLock` MissingNullability: android.app.ActivityManager#scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int) parameter #0: - + Missing nullability on parameter `packages` in method `scheduleApplicationInfoChanged` MissingNullability: android.app.ActivityManager.TaskDescription#getIconFilename(): - + Missing nullability on method `getIconFilename` return MissingNullability: android.app.ActivityTaskManager#clearLaunchParamsForPackages(java.util.List<java.lang.String>) parameter #0: - -MissingNullability: android.app.ActivityTaskManager#listAllStacks(): - -MissingNullability: android.app.ActivityTaskManager#moveTopActivityToPinnedStack(int, android.graphics.Rect) parameter #1: - -MissingNullability: android.app.ActivityTaskManager#removeStacksInWindowingModes(int[]) parameter #0: - -MissingNullability: android.app.ActivityTaskManager#removeStacksWithActivityTypes(int[]) parameter #0: - -MissingNullability: android.app.ActivityTaskManager#resizeDockedStack(android.graphics.Rect, android.graphics.Rect) parameter #0: - -MissingNullability: android.app.ActivityTaskManager#resizeDockedStack(android.graphics.Rect, android.graphics.Rect) parameter #1: - -MissingNullability: android.app.ActivityTaskManager#resizePinnedStack(int, android.graphics.Rect, boolean) parameter #1: - + Missing nullability on parameter `packageNames` in method `clearLaunchParamsForPackages` MissingNullability: android.app.ActivityTaskManager#resizeTask(int, android.graphics.Rect) parameter #1: - -MissingNullability: android.app.ActivityTaskManager#setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean) parameter #4: - + Missing nullability on parameter `bounds` in method `resizeTask` MissingNullability: android.app.ActivityTaskManager#supportsMultiWindow(android.content.Context) parameter #0: - + Missing nullability on parameter `context` in method `supportsMultiWindow` MissingNullability: android.app.ActivityTaskManager#supportsSplitScreenMultiWindow(android.content.Context) parameter #0: - + Missing nullability on parameter `context` in method `supportsSplitScreenMultiWindow` MissingNullability: android.app.AppDetailsActivity#onCreate(android.os.Bundle) parameter #0: - -MissingNullability: android.app.AppOpsManager#getOpStrs(): - + Missing nullability on parameter `savedInstanceState` in method `onCreate` MissingNullability: android.app.AppOpsManager#isOperationActive(int, int, String) parameter #2: - + Missing nullability on parameter `packageName` in method `isOperationActive` MissingNullability: android.app.AppOpsManager#opToPermission(int): - + Missing nullability on method `opToPermission` return MissingNullability: android.app.AppOpsManager#permissionToOpCode(String) parameter #0: - -MissingNullability: android.app.AppOpsManager#setMode(String, int, String, int) parameter #0: - -MissingNullability: android.app.AppOpsManager#setMode(String, int, String, int) parameter #2: - + Missing nullability on parameter `permission` in method `permissionToOpCode` MissingNullability: android.app.AppOpsManager#setMode(int, int, String, int) parameter #2: - -MissingNullability: android.app.AppOpsManager#setUidMode(String, int, int) parameter #0: - -MissingNullability: android.app.AppOpsManager.HistoricalOp#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.app.AppOpsManager.HistoricalOps#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.app.AppOpsManager.HistoricalUidOps#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.app.AppOpsManager.OpEntry#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `packageName` in method `setMode` MissingNullability: android.app.NotificationManager#allowAssistantAdjustment(String) parameter #0: - + Missing nullability on parameter `capability` in method `allowAssistantAdjustment` MissingNullability: android.app.NotificationManager#disallowAssistantAdjustment(String) parameter #0: - + Missing nullability on parameter `capability` in method `disallowAssistantAdjustment` MissingNullability: android.app.NotificationManager#getEffectsSuppressor(): - -MissingNullability: android.app.NotificationManager#matchesCallFilter(android.os.Bundle) parameter #0: - -MissingNullability: android.app.PictureInPictureParams#getActions(): - -MissingNullability: android.app.PictureInPictureParams#getSourceRectHint(): - + Missing nullability on method `getEffectsSuppressor` return MissingNullability: android.app.TimePickerDialog#getTimePicker(): - -MissingNullability: android.app.UiAutomation#executeShellCommandRw(String): - -MissingNullability: android.app.UiAutomation#executeShellCommandRw(String) parameter #0: - -MissingNullability: android.app.UiAutomation#grantRuntimePermission(String, String, android.os.UserHandle) parameter #0: - -MissingNullability: android.app.UiAutomation#grantRuntimePermission(String, String, android.os.UserHandle) parameter #1: - -MissingNullability: android.app.UiAutomation#grantRuntimePermission(String, String, android.os.UserHandle) parameter #2: - -MissingNullability: android.app.UiAutomation#revokeRuntimePermission(String, String, android.os.UserHandle) parameter #0: - -MissingNullability: android.app.UiAutomation#revokeRuntimePermission(String, String, android.os.UserHandle) parameter #1: - -MissingNullability: android.app.UiAutomation#revokeRuntimePermission(String, String, android.os.UserHandle) parameter #2: - -MissingNullability: android.app.WallpaperManager#setWallpaperComponent(android.content.ComponentName) parameter #0: - + Missing nullability on method `getTimePicker` return MissingNullability: android.app.WindowConfiguration#compareTo(android.app.WindowConfiguration) parameter #0: - + Missing nullability on parameter `that` in method `compareTo` MissingNullability: android.app.WindowConfiguration#getAppBounds(): - + Missing nullability on method `getAppBounds` return MissingNullability: android.app.WindowConfiguration#getBounds(): - + Missing nullability on method `getBounds` return MissingNullability: android.app.WindowConfiguration#setAppBounds(android.graphics.Rect) parameter #0: - + Missing nullability on parameter `rect` in method `setAppBounds` MissingNullability: android.app.WindowConfiguration#setBounds(android.graphics.Rect) parameter #0: - + Missing nullability on parameter `rect` in method `setBounds` MissingNullability: android.app.WindowConfiguration#setTo(android.app.WindowConfiguration) parameter #0: - + Missing nullability on parameter `other` in method `setTo` MissingNullability: android.app.WindowConfiguration#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `dest` in method `writeToParcel` MissingNullability: android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts(android.os.UserHandle): - + Missing nullability on method `getOwnerInstalledCaCerts` return MissingNullability: android.app.admin.SecurityLog.SecurityEvent#SecurityEvent(long, byte[]) parameter #1: - -MissingNullability: android.app.backup.BackupManager#getConfigurationIntent(String): - -MissingNullability: android.app.backup.BackupManager#getConfigurationIntent(String) parameter #0: - -MissingNullability: android.app.backup.BackupManager#getDataManagementIntent(String): - -MissingNullability: android.app.backup.BackupManager#getDataManagementIntent(String) parameter #0: - -MissingNullability: android.app.backup.BackupManager#getDestinationString(String): - -MissingNullability: android.app.backup.BackupManager#getDestinationString(String) parameter #0: - -MissingNullability: android.app.prediction.AppPredictionSessionId#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `data` in method `SecurityEvent` MissingNullability: android.app.prediction.AppPredictor#getSessionId(): - -MissingNullability: android.app.prediction.AppTarget#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.app.prediction.AppTargetEvent#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.app.prediction.AppTargetId#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on method `getSessionId` return MissingNullability: android.content.AutofillOptions#forWhitelistingItself(): - + Missing nullability on method `forWhitelistingItself` return MissingNullability: android.content.AutofillOptions#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `parcel` in method `writeToParcel` MissingNullability: android.content.ContentCaptureOptions#forWhitelistingItself(): - + Missing nullability on method `forWhitelistingItself` return MissingNullability: android.content.ContentCaptureOptions#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `parcel` in method `writeToParcel` MissingNullability: android.content.ContentResolver#getSyncAdapterPackagesForAuthorityAsUser(String, int): - + Missing nullability on method `getSyncAdapterPackagesForAuthorityAsUser` return MissingNullability: android.content.ContentResolver#getSyncAdapterPackagesForAuthorityAsUser(String, int) parameter #0: - -MissingNullability: android.content.Context#getDisplay(): - -MissingNullability: android.content.Context#getUser(): - -MissingNullability: android.content.ContextWrapper#getDisplay(): - -MissingNullability: android.content.ContextWrapper#setContentCaptureOptions(android.content.ContentCaptureOptions) parameter #0: - + Missing nullability on parameter `authority` in method `getSyncAdapterPackagesForAuthorityAsUser` MissingNullability: android.content.pm.ActivityInfo#isTranslucentOrFloating(android.content.res.TypedArray) parameter #0: - + Missing nullability on parameter `attributes` in method `isTranslucentOrFloating` MissingNullability: android.content.pm.LauncherApps#LauncherApps(android.content.Context) parameter #0: - -MissingNullability: android.content.pm.PackageInstaller.SessionParams#setGrantedRuntimePermissions(String[]) parameter #0: - + Missing nullability on parameter `context` in method `LauncherApps` MissingNullability: android.content.pm.PackageManager#getHoldLockToken(): - Missing nullability on method `BINDER` return + Missing nullability on method `getHoldLockToken` return MissingNullability: android.content.pm.PackageManager#getNamesForUids(int[]) parameter #0: - + Missing nullability on parameter `uids` in method `getNamesForUids` MissingNullability: android.content.pm.PackageManager#holdLock(android.os.IBinder, int) parameter #0: - + Missing nullability on parameter `token` in method `holdLock` MissingNullability: android.content.pm.ShortcutManager#ShortcutManager(android.content.Context) parameter #0: - + Missing nullability on parameter `context` in method `ShortcutManager` MissingNullability: android.content.pm.UserInfo#UserInfo(android.content.pm.UserInfo) parameter #0: Missing nullability on parameter `orig` in method `UserInfo` MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #1: @@ -942,1469 +292,477 @@ MissingNullability: android.content.pm.UserInfo#userType: MissingNullability: android.content.pm.UserInfo#writeToParcel(android.os.Parcel, int) parameter #0: Missing nullability on parameter `dest` in method `writeToParcel` MissingNullability: android.content.res.AssetManager#getOverlayablesToString(String) parameter #0: - + Missing nullability on parameter `packageName` in method `getOverlayablesToString` MissingNullability: android.content.res.Configuration#windowConfiguration: - -MissingNullability: android.content.rollback.PackageRollbackInfo#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.content.rollback.RollbackInfo#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on field `windowConfiguration` in class `class android.content.res.Configuration` MissingNullability: android.database.sqlite.SQLiteDebug#dump(android.util.Printer, String[]) parameter #0: - + Missing nullability on parameter `printer` in method `dump` MissingNullability: android.database.sqlite.SQLiteDebug#dump(android.util.Printer, String[]) parameter #1: - + Missing nullability on parameter `args` in method `dump` MissingNullability: android.database.sqlite.SQLiteDebug#getDatabaseInfo(): - + Missing nullability on method `getDatabaseInfo` return MissingNullability: android.database.sqlite.SQLiteDebug.DbStats#DbStats(String, long, long, int, int, int, int) parameter #0: - + Missing nullability on parameter `dbName` in method `DbStats` MissingNullability: android.database.sqlite.SQLiteDebug.DbStats#cache: - + Missing nullability on field `cache` in class `class android.database.sqlite.SQLiteDebug.DbStats` MissingNullability: android.database.sqlite.SQLiteDebug.DbStats#dbName: - + Missing nullability on field `dbName` in class `class android.database.sqlite.SQLiteDebug.DbStats` MissingNullability: android.database.sqlite.SQLiteDebug.PagerStats#dbStats: - + Missing nullability on field `dbStats` in class `class android.database.sqlite.SQLiteDebug.PagerStats` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#SQLiteDirectCursorDriver(android.database.sqlite.SQLiteDatabase, String, String, android.os.CancellationSignal) parameter #0: - + Missing nullability on parameter `db` in method `SQLiteDirectCursorDriver` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#SQLiteDirectCursorDriver(android.database.sqlite.SQLiteDatabase, String, String, android.os.CancellationSignal) parameter #1: - + Missing nullability on parameter `sql` in method `SQLiteDirectCursorDriver` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#SQLiteDirectCursorDriver(android.database.sqlite.SQLiteDatabase, String, String, android.os.CancellationSignal) parameter #2: - + Missing nullability on parameter `editTable` in method `SQLiteDirectCursorDriver` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#SQLiteDirectCursorDriver(android.database.sqlite.SQLiteDatabase, String, String, android.os.CancellationSignal) parameter #3: - + Missing nullability on parameter `cancellationSignal` in method `SQLiteDirectCursorDriver` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#cursorRequeried(android.database.Cursor) parameter #0: - + Missing nullability on parameter `cursor` in method `cursorRequeried` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]): - + Missing nullability on method `query` return MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]) parameter #0: - + Missing nullability on parameter `factory` in method `query` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]) parameter #1: - + Missing nullability on parameter `selectionArgs` in method `query` MissingNullability: android.database.sqlite.SQLiteDirectCursorDriver#setBindArguments(String[]) parameter #0: - + Missing nullability on parameter `bindArgs` in method `setBindArguments` MissingNullability: android.database.sqlite.SQLiteGlobal#getDefaultJournalMode(): - + Missing nullability on method `getDefaultJournalMode` return MissingNullability: android.database.sqlite.SQLiteGlobal#getDefaultSyncMode(): - + Missing nullability on method `getDefaultSyncMode` return MissingNullability: android.database.sqlite.SQLiteGlobal#getWALSyncMode(): - + Missing nullability on method `getWALSyncMode` return MissingNullability: android.graphics.ImageDecoder#createSource(android.content.res.Resources, java.io.InputStream, int) parameter #0: - -MissingNullability: android.graphics.ImageDecoder#createSource(android.content.res.Resources, java.io.InputStream, int) parameter #1: - + Missing nullability on parameter `res` in method `createSource` MissingNullability: android.graphics.drawable.AdaptiveIconDrawable#getSafeZone(): - + Missing nullability on method `getSafeZone` return MissingNullability: android.graphics.drawable.ColorDrawable#getXfermode(): - -MissingNullability: android.hardware.camera2.CameraDevice#createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) parameter #0: - + Missing nullability on method `getXfermode` return MissingNullability: android.hardware.camera2.CameraManager#getCameraIdListNoLazy(): - -MissingNullability: android.hardware.display.AmbientBrightnessDayStats#getBucketBoundaries(): - -MissingNullability: android.hardware.display.AmbientBrightnessDayStats#getLocalDate(): - -MissingNullability: android.hardware.display.AmbientBrightnessDayStats#getStats(): - -MissingNullability: android.hardware.display.AmbientBrightnessDayStats#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on method `getCameraIdListNoLazy` return MissingNullability: android.hardware.display.AmbientDisplayConfiguration#AmbientDisplayConfiguration(android.content.Context) parameter #0: - -MissingNullability: android.hardware.display.BrightnessChangeEvent#luxTimestamps: - -MissingNullability: android.hardware.display.BrightnessChangeEvent#luxValues: - -MissingNullability: android.hardware.display.BrightnessChangeEvent#packageName: - -MissingNullability: android.hardware.display.BrightnessChangeEvent#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.hardware.display.BrightnessConfiguration#getCurve(): - -MissingNullability: android.hardware.display.BrightnessConfiguration#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.hardware.display.BrightnessConfiguration.Builder#Builder(float[], float[]) parameter #0: - -MissingNullability: android.hardware.display.BrightnessConfiguration.Builder#Builder(float[], float[]) parameter #1: - -MissingNullability: android.hardware.display.BrightnessCorrection#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.hardware.display.DisplayManager#getAmbientBrightnessStats(): - -MissingNullability: android.hardware.display.DisplayManager#getBrightnessConfiguration(): - -MissingNullability: android.hardware.display.DisplayManager#getBrightnessEvents(): - -MissingNullability: android.hardware.display.DisplayManager#getStableDisplaySize(): - -MissingNullability: android.hardware.display.DisplayManager#setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration) parameter #0: - + Missing nullability on parameter `context` in method `AmbientDisplayConfiguration` MissingNullability: android.location.GnssClock#set(android.location.GnssClock) parameter #0: - + Missing nullability on parameter `clock` in method `set` MissingNullability: android.location.GnssMeasurement#set(android.location.GnssMeasurement) parameter #0: - -MissingNullability: android.location.GnssMeasurementsEvent#GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]) parameter #0: - -MissingNullability: android.location.GnssMeasurementsEvent#GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]) parameter #1: - + Missing nullability on parameter `measurement` in method `set` MissingNullability: android.location.GnssNavigationMessage#set(android.location.GnssNavigationMessage) parameter #0: - + Missing nullability on parameter `navigationMessage` in method `set` MissingNullability: android.location.GnssNavigationMessage#setData(byte[]) parameter #0: - -MissingNullability: android.location.LocationManager#getTestProviderCurrentRequests(String) parameter #0: - -MissingNullability: android.location.LocationRequest#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.media.AudioAttributes#SDK_USAGES: - Missing nullability on field `SDK_USAGES` in class `class android.media.AudioAttributes` + Missing nullability on parameter `value` in method `setData` MissingNullability: android.media.AudioAttributes#getSdkUsages(): Missing nullability on method `getSdkUsages` return -MissingNullability: android.media.AudioFocusInfo#writeToParcel(android.os.Parcel, int) parameter #0: - MissingNullability: android.media.AudioManager#getPublicStreamTypes(): Missing nullability on method `getPublicStreamTypes` return MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String) parameter #3: - + Missing nullability on parameter `clientFormat` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String) parameter #4: - + Missing nullability on parameter `devFormat` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String) parameter #6: - + Missing nullability on parameter `packageName` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #10: - + Missing nullability on parameter `clientEffects` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #11: - + Missing nullability on parameter `deviceEffects` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #3: - + Missing nullability on parameter `clientFormat` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #4: - + Missing nullability on parameter `devFormat` in method `AudioRecordingConfiguration` MissingNullability: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #6: - -MissingNullability: android.media.AudioSystem#streamToString(int): - Missing nullability on method `streamToString` return + Missing nullability on parameter `packageName` in method `AudioRecordingConfiguration` MissingNullability: android.media.PlaybackParams#setAudioStretchMode(int): - + Missing nullability on method `setAudioStretchMode` return MissingNullability: android.media.audiofx.AudioEffect#EFFECT_TYPE_NULL: - + Missing nullability on field `EFFECT_TYPE_NULL` in class `class android.media.audiofx.AudioEffect` MissingNullability: android.media.audiofx.AudioEffect#byteArrayToInt(byte[]) parameter #0: - + Missing nullability on parameter `valueBuf` in method `byteArrayToInt` MissingNullability: android.media.audiofx.AudioEffect#byteArrayToShort(byte[]) parameter #0: - + Missing nullability on parameter `valueBuf` in method `byteArrayToShort` MissingNullability: android.media.audiofx.AudioEffect#getParameter(byte[], byte[]) parameter #0: - + Missing nullability on parameter `param` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#getParameter(byte[], byte[]) parameter #1: - + Missing nullability on parameter `value` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#getParameter(int, byte[]) parameter #1: - + Missing nullability on parameter `value` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#getParameter(int, int[]) parameter #1: - + Missing nullability on parameter `value` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#getParameter(int, short[]) parameter #1: - + Missing nullability on parameter `value` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#getParameter(int[], short[]) parameter #0: - + Missing nullability on parameter `param` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#getParameter(int[], short[]) parameter #1: - + Missing nullability on parameter `value` in method `getParameter` MissingNullability: android.media.audiofx.AudioEffect#intToByteArray(int): - + Missing nullability on method `intToByteArray` return MissingNullability: android.media.audiofx.AudioEffect#isEffectTypeAvailable(java.util.UUID) parameter #0: - + Missing nullability on parameter `type` in method `isEffectTypeAvailable` MissingNullability: android.media.audiofx.AudioEffect#setParameter(byte[], byte[]) parameter #0: - + Missing nullability on parameter `param` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameter(byte[], byte[]) parameter #1: - + Missing nullability on parameter `value` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameter(int, byte[]) parameter #1: - + Missing nullability on parameter `value` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameter(int[], byte[]) parameter #0: - + Missing nullability on parameter `param` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameter(int[], byte[]) parameter #1: - + Missing nullability on parameter `value` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameter(int[], int[]) parameter #0: - + Missing nullability on parameter `param` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameter(int[], int[]) parameter #1: - + Missing nullability on parameter `value` in method `setParameter` MissingNullability: android.media.audiofx.AudioEffect#setParameterListener(android.media.audiofx.AudioEffect.OnParameterChangeListener) parameter #0: - + Missing nullability on parameter `listener` in method `setParameterListener` MissingNullability: android.media.audiofx.AudioEffect#shortToByteArray(short): - + Missing nullability on method `shortToByteArray` return MissingNullability: android.media.audiofx.AudioEffect.Descriptor#Descriptor(android.os.Parcel) parameter #0: - + Missing nullability on parameter `in` in method `Descriptor` MissingNullability: android.media.audiofx.AudioEffect.Descriptor#writeToParcel(android.os.Parcel) parameter #0: - + Missing nullability on parameter `dest` in method `writeToParcel` MissingNullability: android.media.audiofx.AudioEffect.OnParameterChangeListener#onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]) parameter #0: - + Missing nullability on parameter `effect` in method `onParameterChange` MissingNullability: android.media.audiofx.AudioEffect.OnParameterChangeListener#onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]) parameter #2: - + Missing nullability on parameter `param` in method `onParameterChange` MissingNullability: android.media.audiofx.AudioEffect.OnParameterChangeListener#onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]) parameter #3: - -MissingNullability: android.media.audiopolicy.AudioMix.Builder#Builder(android.media.audiopolicy.AudioMixingRule) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioMix.Builder#build(): - -MissingNullability: android.media.audiopolicy.AudioMix.Builder#setDevice(android.media.AudioDeviceInfo): - -MissingNullability: android.media.audiopolicy.AudioMix.Builder#setFormat(android.media.AudioFormat): - -MissingNullability: android.media.audiopolicy.AudioMix.Builder#setFormat(android.media.AudioFormat) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioMix.Builder#setRouteFlags(int): - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#addMixRule(int, Object): - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#addMixRule(int, Object) parameter #1: - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#addRule(android.media.AudioAttributes, int): - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#addRule(android.media.AudioAttributes, int) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#build(): - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#excludeMixRule(int, Object): - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#excludeMixRule(int, Object) parameter #1: - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#excludeRule(android.media.AudioAttributes, int): - -MissingNullability: android.media.audiopolicy.AudioMixingRule.Builder#excludeRule(android.media.AudioAttributes, int) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy#createAudioRecordSink(android.media.audiopolicy.AudioMix): - -MissingNullability: android.media.audiopolicy.AudioPolicy#createAudioRecordSink(android.media.audiopolicy.AudioMix) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy#createAudioTrackSource(android.media.audiopolicy.AudioMix): - -MissingNullability: android.media.audiopolicy.AudioPolicy#createAudioTrackSource(android.media.audiopolicy.AudioMix) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy#setRegistration(String) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy#toLogFriendlyString(): - -MissingNullability: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener#onAudioFocusAbandon(android.media.AudioFocusInfo) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener#onAudioFocusGrant(android.media.AudioFocusInfo, int) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener#onAudioFocusLoss(android.media.AudioFocusInfo, boolean) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(android.media.AudioFocusInfo, int) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener#onMixStateUpdate(android.media.audiopolicy.AudioMix) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.Builder#Builder(android.content.Context) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener) parameter #0: - -MissingNullability: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener) parameter #0: - -MissingNullability: android.metrics.LogMaker#LogMaker(Object[]) parameter #0: - -MissingNullability: android.metrics.LogMaker#addTaggedData(int, Object): - -MissingNullability: android.metrics.LogMaker#addTaggedData(int, Object) parameter #1: - -MissingNullability: android.metrics.LogMaker#clearCategory(): - -MissingNullability: android.metrics.LogMaker#clearPackageName(): - -MissingNullability: android.metrics.LogMaker#clearSubtype(): - -MissingNullability: android.metrics.LogMaker#clearTaggedData(int): - -MissingNullability: android.metrics.LogMaker#clearType(): - -MissingNullability: android.metrics.LogMaker#deserialize(Object[]) parameter #0: - -MissingNullability: android.metrics.LogMaker#getCounterName(): - -MissingNullability: android.metrics.LogMaker#getPackageName(): - -MissingNullability: android.metrics.LogMaker#getTaggedData(int): - -MissingNullability: android.metrics.LogMaker#isSubsetOf(android.metrics.LogMaker) parameter #0: - -MissingNullability: android.metrics.LogMaker#isValidValue(Object) parameter #0: - -MissingNullability: android.metrics.LogMaker#serialize(): - -MissingNullability: android.metrics.LogMaker#setCategory(int): - -MissingNullability: android.metrics.LogMaker#setPackageName(String): - -MissingNullability: android.metrics.LogMaker#setPackageName(String) parameter #0: - -MissingNullability: android.metrics.LogMaker#setSubtype(int): - -MissingNullability: android.metrics.LogMaker#setType(int): - -MissingNullability: android.metrics.MetricsReader#next(): - -MissingNullability: android.net.NetworkCapabilities#getCapabilities(): - -MissingNullability: android.net.StaticIpConfiguration#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.net.TestNetworkInterface#CREATOR: - -MissingNullability: android.net.TestNetworkInterface#TestNetworkInterface(android.os.ParcelFileDescriptor, String) parameter #0: - -MissingNullability: android.net.TestNetworkInterface#TestNetworkInterface(android.os.ParcelFileDescriptor, String) parameter #1: - -MissingNullability: android.net.TestNetworkInterface#getFileDescriptor(): - -MissingNullability: android.net.TestNetworkInterface#getInterfaceName(): - -MissingNullability: android.net.TestNetworkInterface#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.net.TestNetworkManager#createTapInterface(): - -MissingNullability: android.net.TestNetworkManager#createTunInterface(android.net.LinkAddress[]): - -MissingNullability: android.net.apf.ApfCapabilities#CREATOR: - -MissingNullability: android.net.apf.ApfCapabilities#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.net.metrics.DhcpClientEvent.Builder#setMsg(String) parameter #0: - + Missing nullability on parameter `value` in method `onParameterChange` MissingNullability: android.os.Build#is64BitAbi(String) parameter #0: - + Missing nullability on parameter `abi` in method `is64BitAbi` MissingNullability: android.os.Build.VERSION#ACTIVE_CODENAMES: - + Missing nullability on field `ACTIVE_CODENAMES` in class `class android.os.Build.VERSION` MissingNullability: android.os.Environment#buildPath(java.io.File, java.lang.String...): - + Missing nullability on method `buildPath` return MissingNullability: android.os.Environment#buildPath(java.io.File, java.lang.String...) parameter #0: - + Missing nullability on parameter `base` in method `buildPath` MissingNullability: android.os.Environment#buildPath(java.io.File, java.lang.String...) parameter #1: - + Missing nullability on parameter `segments` in method `buildPath` MissingNullability: android.os.FileUtils#contains(java.io.File, java.io.File) parameter #0: - + Missing nullability on parameter `dir` in method `contains` MissingNullability: android.os.FileUtils#contains(java.io.File, java.io.File) parameter #1: - -MissingNullability: android.os.HwBinder#getService(String, String): - -MissingNullability: android.os.HwBinder#getService(String, String) parameter #0: - -MissingNullability: android.os.HwBinder#getService(String, String) parameter #1: - -MissingNullability: android.os.HwBinder#getService(String, String, boolean): - -MissingNullability: android.os.HwBinder#getService(String, String, boolean) parameter #0: - -MissingNullability: android.os.HwBinder#getService(String, String, boolean) parameter #1: - -MissingNullability: android.os.HwBinder#onTransact(int, android.os.HwParcel, android.os.HwParcel, int) parameter #1: - -MissingNullability: android.os.HwBinder#onTransact(int, android.os.HwParcel, android.os.HwParcel, int) parameter #2: - -MissingNullability: android.os.HwBinder#registerService(String) parameter #0: - -MissingNullability: android.os.HwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int) parameter #1: - -MissingNullability: android.os.HwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int) parameter #2: - -MissingNullability: android.os.HwBlob#copyToBoolArray(long, boolean[], int) parameter #1: - -MissingNullability: android.os.HwBlob#copyToDoubleArray(long, double[], int) parameter #1: - -MissingNullability: android.os.HwBlob#copyToFloatArray(long, float[], int) parameter #1: - -MissingNullability: android.os.HwBlob#copyToInt16Array(long, short[], int) parameter #1: - -MissingNullability: android.os.HwBlob#copyToInt32Array(long, int[], int) parameter #1: - -MissingNullability: android.os.HwBlob#copyToInt64Array(long, long[], int) parameter #1: - -MissingNullability: android.os.HwBlob#copyToInt8Array(long, byte[], int) parameter #1: - -MissingNullability: android.os.HwBlob#getString(long): - -MissingNullability: android.os.HwBlob#putBlob(long, android.os.HwBlob) parameter #1: - -MissingNullability: android.os.HwBlob#putBoolArray(long, boolean[]) parameter #1: - -MissingNullability: android.os.HwBlob#putDoubleArray(long, double[]) parameter #1: - -MissingNullability: android.os.HwBlob#putFloatArray(long, float[]) parameter #1: - -MissingNullability: android.os.HwBlob#putInt16Array(long, short[]) parameter #1: - -MissingNullability: android.os.HwBlob#putInt32Array(long, int[]) parameter #1: - -MissingNullability: android.os.HwBlob#putInt64Array(long, long[]) parameter #1: - -MissingNullability: android.os.HwBlob#putInt8Array(long, byte[]) parameter #1: - -MissingNullability: android.os.HwBlob#putString(long, String) parameter #1: - -MissingNullability: android.os.HwBlob#wrapArray(boolean[]): - -MissingNullability: android.os.HwBlob#wrapArray(byte[]): - -MissingNullability: android.os.HwBlob#wrapArray(double[]): - -MissingNullability: android.os.HwBlob#wrapArray(float[]): - -MissingNullability: android.os.HwBlob#wrapArray(int[]): - -MissingNullability: android.os.HwBlob#wrapArray(long[]): - -MissingNullability: android.os.HwBlob#wrapArray(short[]): - -MissingNullability: android.os.HwParcel#enforceInterface(String) parameter #0: - -MissingNullability: android.os.HwParcel#readBoolVector(): - -MissingNullability: android.os.HwParcel#readBuffer(long): - -MissingNullability: android.os.HwParcel#readDoubleVector(): - -MissingNullability: android.os.HwParcel#readEmbeddedBuffer(long, long, long, boolean): - -MissingNullability: android.os.HwParcel#readFloatVector(): - -MissingNullability: android.os.HwParcel#readInt16Vector(): - -MissingNullability: android.os.HwParcel#readInt32Vector(): - -MissingNullability: android.os.HwParcel#readInt64Vector(): - -MissingNullability: android.os.HwParcel#readInt8Vector(): - -MissingNullability: android.os.HwParcel#readString(): - -MissingNullability: android.os.HwParcel#readStringVector(): - -MissingNullability: android.os.HwParcel#readStrongBinder(): - -MissingNullability: android.os.HwParcel#writeBoolVector(java.util.ArrayList<java.lang.Boolean>) parameter #0: - -MissingNullability: android.os.HwParcel#writeBuffer(android.os.HwBlob) parameter #0: - -MissingNullability: android.os.HwParcel#writeDoubleVector(java.util.ArrayList<java.lang.Double>) parameter #0: - -MissingNullability: android.os.HwParcel#writeFloatVector(java.util.ArrayList<java.lang.Float>) parameter #0: - -MissingNullability: android.os.HwParcel#writeInt16Vector(java.util.ArrayList<java.lang.Short>) parameter #0: - -MissingNullability: android.os.HwParcel#writeInt32Vector(java.util.ArrayList<java.lang.Integer>) parameter #0: - -MissingNullability: android.os.HwParcel#writeInt64Vector(java.util.ArrayList<java.lang.Long>) parameter #0: - -MissingNullability: android.os.HwParcel#writeInt8Vector(java.util.ArrayList<java.lang.Byte>) parameter #0: - -MissingNullability: android.os.HwParcel#writeInterfaceToken(String) parameter #0: - -MissingNullability: android.os.HwParcel#writeString(String) parameter #0: - -MissingNullability: android.os.HwParcel#writeStringVector(java.util.ArrayList<java.lang.String>) parameter #0: - -MissingNullability: android.os.HwParcel#writeStrongBinder(android.os.IHwBinder) parameter #0: - -MissingNullability: android.os.IHwBinder#linkToDeath(android.os.IHwBinder.DeathRecipient, long) parameter #0: - -MissingNullability: android.os.IHwBinder#queryLocalInterface(String): - -MissingNullability: android.os.IHwBinder#queryLocalInterface(String) parameter #0: - -MissingNullability: android.os.IHwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int) parameter #1: - -MissingNullability: android.os.IHwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int) parameter #2: - -MissingNullability: android.os.IHwBinder#unlinkToDeath(android.os.IHwBinder.DeathRecipient) parameter #0: - -MissingNullability: android.os.IHwInterface#asBinder(): - -MissingNullability: android.os.IncidentManager#approveReport(android.net.Uri) parameter #0: - -MissingNullability: android.os.IncidentManager#cancelAuthorization(android.os.IncidentManager.AuthListener) parameter #0: - -MissingNullability: android.os.IncidentManager#deleteIncidentReports(android.net.Uri) parameter #0: - -MissingNullability: android.os.IncidentManager#denyReport(android.net.Uri) parameter #0: - -MissingNullability: android.os.IncidentManager#getIncidentReport(android.net.Uri) parameter #0: - -MissingNullability: android.os.IncidentManager#getIncidentReportList(String) parameter #0: - -MissingNullability: android.os.IncidentManager#getPendingReports(): - -MissingNullability: android.os.IncidentManager#reportIncident(android.os.IncidentReportArgs) parameter #0: - -MissingNullability: android.os.IncidentManager#requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener) parameter #1: - -MissingNullability: android.os.IncidentManager#requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener) parameter #3: - -MissingNullability: android.os.IncidentManager.IncidentReport#IncidentReport(android.os.Parcel) parameter #0: - -MissingNullability: android.os.IncidentManager.IncidentReport#getInputStream(): - -MissingNullability: android.os.IncidentManager.IncidentReport#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.os.IncidentReportArgs#IncidentReportArgs(android.os.Parcel) parameter #0: - -MissingNullability: android.os.IncidentReportArgs#addHeader(byte[]) parameter #0: - -MissingNullability: android.os.IncidentReportArgs#readFromParcel(android.os.Parcel) parameter #0: - -MissingNullability: android.os.IncidentReportArgs#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `file` in method `contains` MissingNullability: android.os.ParcelFileDescriptor#getFile(java.io.FileDescriptor): - + Missing nullability on method `getFile` return MissingNullability: android.os.ParcelFileDescriptor#getFile(java.io.FileDescriptor) parameter #0: - -MissingNullability: android.os.RemoteCallback#RemoteCallback(android.os.RemoteCallback.OnResultListener) parameter #0: - -MissingNullability: android.os.RemoteCallback#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `fd` in method `getFile` MissingNullability: android.os.StrictMode#setViolationLogger(android.os.StrictMode.ViolationLogger) parameter #0: - + Missing nullability on parameter `listener` in method `setViolationLogger` MissingNullability: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel) parameter #0: - + Missing nullability on parameter `in` in method `ViolationInfo` MissingNullability: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel, boolean) parameter #0: - + Missing nullability on parameter `in` in method `ViolationInfo` MissingNullability: android.os.StrictMode.ViolationInfo#broadcastIntentAction: - + Missing nullability on field `broadcastIntentAction` in class `class android.os.StrictMode.ViolationInfo` MissingNullability: android.os.StrictMode.ViolationInfo#dump(android.util.Printer, String) parameter #0: - + Missing nullability on parameter `pw` in method `dump` MissingNullability: android.os.StrictMode.ViolationInfo#dump(android.util.Printer, String) parameter #1: - + Missing nullability on parameter `prefix` in method `dump` MissingNullability: android.os.StrictMode.ViolationInfo#getStackTrace(): - + Missing nullability on method `getStackTrace` return MissingNullability: android.os.StrictMode.ViolationInfo#getViolationClass(): - + Missing nullability on method `getViolationClass` return MissingNullability: android.os.StrictMode.ViolationInfo#getViolationDetails(): - + Missing nullability on method `getViolationDetails` return MissingNullability: android.os.StrictMode.ViolationInfo#tags: - + Missing nullability on field `tags` in class `class android.os.StrictMode.ViolationInfo` MissingNullability: android.os.StrictMode.ViolationInfo#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `dest` in method `writeToParcel` MissingNullability: android.os.StrictMode.ViolationLogger#log(android.os.StrictMode.ViolationInfo) parameter #0: - -MissingNullability: android.os.UserHandle#of(int): - + Missing nullability on parameter `info` in method `log` MissingNullability: android.os.VibrationEffect#RINGTONES: - + Missing nullability on field `RINGTONES` in class `class android.os.VibrationEffect` MissingNullability: android.os.VibrationEffect#get(android.net.Uri, android.content.Context) parameter #0: - + Missing nullability on parameter `uri` in method `get` MissingNullability: android.os.VibrationEffect#get(android.net.Uri, android.content.Context) parameter #1: - + Missing nullability on parameter `context` in method `get` MissingNullability: android.os.VibrationEffect#get(int): - + Missing nullability on method `get` return MissingNullability: android.os.VibrationEffect#get(int, boolean): - + Missing nullability on method `get` return MissingNullability: android.os.VintfObject#getHalNamesAndVersions(): - + Missing nullability on method `getHalNamesAndVersions` return MissingNullability: android.os.VintfObject#getSepolicyVersion(): - + Missing nullability on method `getSepolicyVersion` return MissingNullability: android.os.VintfObject#getTargetFrameworkCompatibilityMatrixVersion(): - + Missing nullability on method `getTargetFrameworkCompatibilityMatrixVersion` return MissingNullability: android.os.VintfObject#getVndkSnapshots(): - + Missing nullability on method `getVndkSnapshots` return MissingNullability: android.os.VintfObject#report(): - + Missing nullability on method `report` return MissingNullability: android.os.VintfRuntimeInfo#getCpuInfo(): - + Missing nullability on method `getCpuInfo` return MissingNullability: android.os.VintfRuntimeInfo#getHardwareId(): - + Missing nullability on method `getHardwareId` return MissingNullability: android.os.VintfRuntimeInfo#getKernelVersion(): - + Missing nullability on method `getKernelVersion` return MissingNullability: android.os.VintfRuntimeInfo#getNodeName(): - + Missing nullability on method `getNodeName` return MissingNullability: android.os.VintfRuntimeInfo#getOsName(): - + Missing nullability on method `getOsName` return MissingNullability: android.os.VintfRuntimeInfo#getOsRelease(): - + Missing nullability on method `getOsRelease` return MissingNullability: android.os.VintfRuntimeInfo#getOsVersion(): - + Missing nullability on method `getOsVersion` return MissingNullability: android.os.WorkSource#add(int, String) parameter #1: - -MissingNullability: android.os.WorkSource#addReturningNewbs(android.os.WorkSource) parameter #0: - -MissingNullability: android.os.WorkSource#getName(int): - -MissingNullability: android.os.WorkSource#setReturningDiffs(android.os.WorkSource) parameter #0: - + Missing nullability on parameter `name` in method `add` MissingNullability: android.os.health.HealthKeys.Constants#Constants(Class) parameter #0: - + Missing nullability on parameter `clazz` in method `Constants` MissingNullability: android.os.health.HealthKeys.Constants#getDataType(): - + Missing nullability on method `getDataType` return MissingNullability: android.os.health.HealthKeys.Constants#getKeys(int): - + Missing nullability on method `getKeys` return MissingNullability: android.os.health.HealthStats#HealthStats(android.os.Parcel) parameter #0: - + Missing nullability on parameter `in` in method `HealthStats` MissingNullability: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel) parameter #0: - + Missing nullability on parameter `in` in method `HealthStatsParceler` MissingNullability: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.health.HealthStatsWriter) parameter #0: - + Missing nullability on parameter `writer` in method `HealthStatsParceler` MissingNullability: android.os.health.HealthStatsParceler#getHealthStats(): - + Missing nullability on method `getHealthStats` return MissingNullability: android.os.health.HealthStatsParceler#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `out` in method `writeToParcel` MissingNullability: android.os.health.HealthStatsWriter#HealthStatsWriter(android.os.health.HealthKeys.Constants) parameter #0: - + Missing nullability on parameter `constants` in method `HealthStatsWriter` MissingNullability: android.os.health.HealthStatsWriter#addMeasurements(int, String, long) parameter #1: - + Missing nullability on parameter `name` in method `addMeasurements` MissingNullability: android.os.health.HealthStatsWriter#addStats(int, String, android.os.health.HealthStatsWriter) parameter #1: - + Missing nullability on parameter `name` in method `addStats` MissingNullability: android.os.health.HealthStatsWriter#addStats(int, String, android.os.health.HealthStatsWriter) parameter #2: - + Missing nullability on parameter `value` in method `addStats` MissingNullability: android.os.health.HealthStatsWriter#addTimers(int, String, android.os.health.TimerStat) parameter #1: - + Missing nullability on parameter `name` in method `addTimers` MissingNullability: android.os.health.HealthStatsWriter#addTimers(int, String, android.os.health.TimerStat) parameter #2: - + Missing nullability on parameter `value` in method `addTimers` MissingNullability: android.os.health.HealthStatsWriter#flattenToParcel(android.os.Parcel) parameter #0: - + Missing nullability on parameter `out` in method `flattenToParcel` MissingNullability: android.os.storage.StorageVolume#getPath(): - -MissingNullability: android.permission.RuntimePermissionPresentationInfo#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on method `getPath` return MissingNullability: android.provider.CalendarContract.Calendars#SYNC_WRITABLE_COLUMNS: - + Missing nullability on field `SYNC_WRITABLE_COLUMNS` in class `class android.provider.CalendarContract.Calendars` MissingNullability: android.provider.CalendarContract.Events#SYNC_WRITABLE_COLUMNS: - -MissingNullability: android.provider.ContactsContract.CommonDataKinds.Phone#ENTERPRISE_CONTENT_URI: - + Missing nullability on field `SYNC_WRITABLE_COLUMNS` in class `class android.provider.CalendarContract.Events` MissingNullability: android.provider.ContactsContract.RawContactsEntity#CORP_CONTENT_URI: - -MissingNullability: android.provider.DeviceConfig#getProperty(String, String): - -MissingNullability: android.provider.DeviceConfig#getString(String, String, String): - -MissingNullability: android.provider.MediaStore#deleteContributedMedia(android.content.Context, String, android.os.UserHandle) parameter #0: - -MissingNullability: android.provider.MediaStore#deleteContributedMedia(android.content.Context, String, android.os.UserHandle) parameter #1: - -MissingNullability: android.provider.MediaStore#deleteContributedMedia(android.content.Context, String, android.os.UserHandle) parameter #2: - -MissingNullability: android.provider.MediaStore#getContributedMediaSize(android.content.Context, String, android.os.UserHandle) parameter #0: - -MissingNullability: android.provider.MediaStore#getContributedMediaSize(android.content.Context, String, android.os.UserHandle) parameter #1: - -MissingNullability: android.provider.MediaStore#getContributedMediaSize(android.content.Context, String, android.os.UserHandle) parameter #2: - -MissingNullability: android.provider.MediaStore#scanFile(android.content.Context, java.io.File): - -MissingNullability: android.provider.MediaStore#scanFile(android.content.Context, java.io.File) parameter #0: - -MissingNullability: android.provider.MediaStore#scanFile(android.content.Context, java.io.File) parameter #1: - -MissingNullability: android.provider.MediaStore#scanFileFromShell(android.content.Context, java.io.File): - -MissingNullability: android.provider.MediaStore#scanFileFromShell(android.content.Context, java.io.File) parameter #0: - -MissingNullability: android.provider.MediaStore#scanFileFromShell(android.content.Context, java.io.File) parameter #1: - -MissingNullability: android.provider.MediaStore#scanVolume(android.content.Context, java.io.File) parameter #0: - -MissingNullability: android.provider.MediaStore#scanVolume(android.content.Context, java.io.File) parameter #1: - -MissingNullability: android.provider.MediaStore#waitForIdle(android.content.Context) parameter #0: - -MissingNullability: android.security.KeyStoreException#KeyStoreException(int, String) parameter #1: - -MissingNullability: android.security.keystore.AttestationUtils#attestDeviceIds(android.content.Context, int[], byte[]) parameter #0: - + Missing nullability on field `CORP_CONTENT_URI` in class `class android.provider.ContactsContract.RawContactsEntity` MissingNullability: android.security.keystore.KeyProtection.Builder#setBoundToSpecificSecureUserId(long): - -MissingNullability: android.service.autofill.AutofillFieldClassificationService#onBind(android.content.Intent): - -MissingNullability: android.service.autofill.AutofillFieldClassificationService#onBind(android.content.Intent) parameter #0: - + Missing nullability on method `setBoundToSpecificSecureUserId` return MissingNullability: android.service.autofill.CompositeUserData#getCategoryIds(): - + Missing nullability on method `getCategoryIds` return MissingNullability: android.service.autofill.CompositeUserData#getDefaultFieldClassificationArgs(): - + Missing nullability on method `getDefaultFieldClassificationArgs` return MissingNullability: android.service.autofill.CompositeUserData#getFieldClassificationAlgorithms(): - + Missing nullability on method `getFieldClassificationAlgorithms` return MissingNullability: android.service.autofill.CompositeUserData#getFieldClassificationArgs(): - + Missing nullability on method `getFieldClassificationArgs` return MissingNullability: android.service.autofill.CompositeUserData#getValues(): - + Missing nullability on method `getValues` return MissingNullability: android.service.autofill.CompositeUserData#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `parcel` in method `writeToParcel` MissingNullability: android.service.autofill.UserData#getFieldClassificationAlgorithms(): - -MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: - -MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1: - -MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2: - -MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0: - -MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: - -MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1: - -MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2: - -MissingNullability: android.service.notification.Adjustment#Adjustment(String, String, android.os.Bundle, CharSequence, int) parameter #0: - -MissingNullability: android.service.notification.Adjustment#Adjustment(String, String, android.os.Bundle, CharSequence, int) parameter #1: - -MissingNullability: android.service.notification.Adjustment#Adjustment(String, String, android.os.Bundle, CharSequence, int) parameter #2: - -MissingNullability: android.service.notification.Adjustment#Adjustment(String, String, android.os.Bundle, CharSequence, int) parameter #3: - -MissingNullability: android.service.notification.Adjustment#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0: - -MissingNullability: android.service.notification.NotificationStats#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.service.notification.SnoozeCriterion#SnoozeCriterion(String, CharSequence, CharSequence) parameter #0: - -MissingNullability: android.service.notification.SnoozeCriterion#SnoozeCriterion(String, CharSequence, CharSequence) parameter #1: - -MissingNullability: android.service.notification.SnoozeCriterion#SnoozeCriterion(String, CharSequence, CharSequence) parameter #2: - -MissingNullability: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel) parameter #0: - -MissingNullability: android.service.notification.SnoozeCriterion#getConfirmation(): - -MissingNullability: android.service.notification.SnoozeCriterion#getExplanation(): - -MissingNullability: android.service.notification.SnoozeCriterion#getId(): - -MissingNullability: android.service.notification.SnoozeCriterion#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on method `getFieldClassificationAlgorithms` return MissingNullability: android.telecom.Call.Details#getTelecomCallId(): - -MissingNullability: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallFurther(boolean): - -MissingNullability: android.telecom.Conference#getPrimaryConnection(): - -MissingNullability: android.telecom.PhoneAccountSuggestionService#onBind(android.content.Intent): - -MissingNullability: android.telecom.PhoneAccountSuggestionService#onBind(android.content.Intent) parameter #0: - -MissingNullability: android.telephony.CallQuality#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.DataSpecificRegistrationInfo#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.LteVopsSupportInfo#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.NetworkRegistrationInfo#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on method `getTelecomCallId` return MissingNullability: android.telephony.ServiceState#addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo) parameter #0: - + Missing nullability on parameter `nri` in method `addNetworkRegistrationInfo` MissingNullability: android.telephony.ServiceState#setCellBandwidths(int[]) parameter #0: - + Missing nullability on parameter `bandwidths` in method `setCellBandwidths` MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(String, String) parameter #0: - + Missing nullability on parameter `destAddress` in method `checkSmsShortCodeDestination` MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(String, String) parameter #1: - -MissingNullability: android.telephony.TelephonyManager#checkCarrierPrivilegesForPackage(String) parameter #0: - -MissingNullability: android.telephony.TelephonyManager#getCarrierPackageNamesForIntent(android.content.Intent): - -MissingNullability: android.telephony.TelephonyManager#getCarrierPackageNamesForIntent(android.content.Intent) parameter #0: - + Missing nullability on parameter `countryIso` in method `checkSmsShortCodeDestination` MissingNullability: android.telephony.TelephonyManager#getLine1AlphaTag(): - + Missing nullability on method `getLine1AlphaTag` return MissingNullability: android.telephony.TelephonyManager#getRadioHalVersion(): - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #0: - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #1: - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #2: - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #3: - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #4: - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #5: - -MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String) parameter #6: - + Missing nullability on method `getRadioHalVersion` return MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #0: - + Missing nullability on parameter `mccmnc` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #1: - + Missing nullability on parameter `imsi` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #2: - + Missing nullability on parameter `iccid` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #3: - + Missing nullability on parameter `gid1` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #4: - + Missing nullability on parameter `gid2` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #5: - + Missing nullability on parameter `plmn` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #6: - + Missing nullability on parameter `spn` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #7: - + Missing nullability on parameter `carrierPriviledgeRules` in method `setCarrierTestOverride` MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #8: - -MissingNullability: android.telephony.ims.ImsCallForwardInfo#getNumber(): - -MissingNullability: android.telephony.ims.ImsCallForwardInfo#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile) parameter #2: - -MissingNullability: android.telephony.ims.ImsCallProfile#ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile) parameter #3: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String): - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String, String): - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String, String) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String, String) parameter #1: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraBoolean(String) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraBoolean(String, boolean) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraInt(String) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraInt(String, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtras(): - -MissingNullability: android.telephony.ims.ImsCallProfile#getMediaProfile(): - -MissingNullability: android.telephony.ims.ImsCallProfile#getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtra(String, String) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtra(String, String) parameter #1: - -MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtraBoolean(String, boolean) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtraInt(String, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#updateCallExtras(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#updateCallType(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#updateMediaProfile(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallProfile#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtendFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #1: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #1: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo) parameter #2: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo) parameter #2: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHeld(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHoldFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHoldReceived(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionInitiated(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #1: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionProgressing(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionRemoveParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionResumeFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionResumeReceived(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionResumed(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionRttMessageReceived(String) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionSuppServiceReceived(android.telephony.ims.ImsSuppServiceNotification) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionTerminated(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUpdateFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUpdateReceived(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUpdated(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUssdMessageReceived(int, String) parameter #1: - -MissingNullability: android.telephony.ims.ImsConferenceState#getConnectionStateForStatus(String) parameter #0: - -MissingNullability: android.telephony.ims.ImsConferenceState#mParticipants: - -MissingNullability: android.telephony.ims.ImsConferenceState#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsExternalCallState#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsReasonInfo#ImsReasonInfo(int, int, String) parameter #2: - -MissingNullability: android.telephony.ims.ImsReasonInfo#getExtraMessage(): - -MissingNullability: android.telephony.ims.ImsReasonInfo#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsService#createMmTelFeature(int): - -MissingNullability: android.telephony.ims.ImsService#createRcsFeature(int): - -MissingNullability: android.telephony.ims.ImsService#getConfig(int): - -MissingNullability: android.telephony.ims.ImsService#getRegistration(int): - -MissingNullability: android.telephony.ims.ImsService#onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) parameter #0: - -MissingNullability: android.telephony.ims.ImsService#querySupportedImsFeatures(): - -MissingNullability: android.telephony.ims.ImsSsData#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsSsInfo#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsStreamMediaProfile#copyFrom(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsStreamMediaProfile#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsSuppServiceNotification#ImsSuppServiceNotification(int, int, int, int, String, String[]) parameter #4: - -MissingNullability: android.telephony.ims.ImsSuppServiceNotification#ImsSuppServiceNotification(int, int, int, int, String, String[]) parameter #5: - -MissingNullability: android.telephony.ims.ImsSuppServiceNotification#history: - -MissingNullability: android.telephony.ims.ImsSuppServiceNotification#number: - -MissingNullability: android.telephony.ims.ImsSuppServiceNotification#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsUtListener#onSupplementaryServiceIndication(android.telephony.ims.ImsSsData) parameter #0: - -MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationCallBarringQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - -MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationCallForwardQueried(int, android.telephony.ims.ImsCallForwardInfo[]) parameter #1: - -MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationCallWaitingQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - -MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationQueried(int, android.os.Bundle) parameter #1: - -MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationQueryFailed(int, android.telephony.ims.ImsReasonInfo) parameter #1: - -MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationUpdateFailed(int, android.telephony.ims.ImsReasonInfo) parameter #1: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSendSessionModifyRequest(android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSendSessionModifyRequest(android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #1: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSendSessionModifyResponse(android.telecom.VideoProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetCamera(String) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetCamera(String, int) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetDisplaySurface(android.view.Surface) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetPauseImage(android.net.Uri) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetPreviewSurface(android.view.Surface) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#receiveSessionModifyRequest(android.telecom.VideoProfile) parameter #0: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #1: - -MissingNullability: android.telephony.ims.ImsVideoCallProvider#receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #2: - -MissingNullability: android.telephony.ims.feature.CapabilityChangeRequest#getCapabilitiesToDisable(): - -MissingNullability: android.telephony.ims.feature.CapabilityChangeRequest#getCapabilitiesToEnable(): - -MissingNullability: android.telephony.ims.feature.CapabilityChangeRequest#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.feature.ImsFeature#changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy) parameter #0: - -MissingNullability: android.telephony.ims.feature.ImsFeature#changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy) parameter #1: - -MissingNullability: android.telephony.ims.feature.MmTelFeature#queryCapabilityStatus(): - -MissingNullability: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#accept(int, android.telephony.ims.ImsStreamMediaProfile) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#deflect(String) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#extendToConference(String[]) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getCallId(): - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getCallProfile(): - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getImsVideoCallProvider(): - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getLocalCallProfile(): - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getProperty(String): - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getProperty(String) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getRemoteCallProfile(): - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#hold(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#inviteParticipants(String[]) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#removeParticipants(String[]) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#resume(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendDtmf(char, android.os.Message) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendRttMessage(String) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendRttModifyRequest(android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendUssd(String) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#setListener(android.telephony.ims.ImsCallSessionListener) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#start(String, android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#start(String, android.telephony.ims.ImsCallProfile) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#startConference(String[], android.telephony.ims.ImsCallProfile) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#startConference(String[], android.telephony.ims.ImsCallProfile) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#update(int, android.telephony.ims.ImsStreamMediaProfile) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase.State#toString(int): - -MissingNullability: android.telephony.ims.stub.ImsConfigImplBase#getConfigString(int): - -MissingNullability: android.telephony.ims.stub.ImsConfigImplBase#notifyProvisionedValueChanged(int, String) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsConfigImplBase#setConfig(int, String) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration#getServiceFeatures(): - -MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#addFeature(int, int): - -MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#build(): - -MissingNullability: android.telephony.ims.stub.ImsMultiEndpointImplBase#onImsExternalCallStateUpdate(java.util.List<android.telephony.ims.ImsExternalCallState>) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsRegistrationImplBase#onDeregistered(android.telephony.ims.ImsReasonInfo) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsRegistrationImplBase#onSubscriberAssociatedUriChanged(android.net.Uri[]) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsRegistrationImplBase#onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#getSmsFormat(): - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsReceived(int, String, byte[]) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsReceived(int, String, byte[]) parameter #2: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #2: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, int, String, byte[]) parameter #2: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, int, String, byte[]) parameter #3: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#sendSms(int, int, String, String, boolean, byte[]) parameter #2: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#sendSms(int, int, String, String, boolean, byte[]) parameter #3: - -MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#sendSms(int, int, String, String, boolean, byte[]) parameter #5: - -MissingNullability: android.telephony.ims.stub.ImsUtImplBase#queryCallForward(int, String) parameter #1: - -MissingNullability: android.telephony.ims.stub.ImsUtImplBase#setListener(android.telephony.ims.ImsUtListener) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsUtImplBase#transact(android.os.Bundle) parameter #0: - -MissingNullability: android.telephony.ims.stub.ImsUtImplBase#updateCallBarring(int, int, String[]) parameter #2: - -MissingNullability: android.telephony.ims.stub.ImsUtImplBase#updateCallBarringForServiceClass(int, int, String[], int) parameter #2: - -MissingNullability: android.telephony.ims.stub.ImsUtImplBase#updateCallForward(int, int, String, int, int) parameter #2: - -MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): - -MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0: - -MissingNullability: android.telephony.mbms.FileInfo#FileInfo(android.net.Uri, String) parameter #0: - -MissingNullability: android.telephony.mbms.FileInfo#FileInfo(android.net.Uri, String) parameter #1: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #0: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #1: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #2: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #3: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #4: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #5: - -MissingNullability: android.telephony.mbms.FileServiceInfo#FileServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>) parameter #6: - -MissingNullability: android.telephony.mbms.StreamingServiceInfo#StreamingServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date) parameter #0: - -MissingNullability: android.telephony.mbms.StreamingServiceInfo#StreamingServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date) parameter #1: - -MissingNullability: android.telephony.mbms.StreamingServiceInfo#StreamingServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date) parameter #2: - -MissingNullability: android.telephony.mbms.StreamingServiceInfo#StreamingServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date) parameter #3: - -MissingNullability: android.telephony.mbms.StreamingServiceInfo#StreamingServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date) parameter #4: - -MissingNullability: android.telephony.mbms.StreamingServiceInfo#StreamingServiceInfo(java.util.Map<java.util.Locale,java.lang.String>, String, java.util.List<java.util.Locale>, String, java.util.Date, java.util.Date) parameter #5: - -MissingNullability: android.telephony.mbms.UriPathPair#getContentUri(): - -MissingNullability: android.telephony.mbms.UriPathPair#getFilePathUri(): - -MissingNullability: android.telephony.mbms.UriPathPair#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#asBinder(): - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#cancelDownload(android.telephony.mbms.DownloadRequest) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#download(android.telephony.mbms.DownloadRequest) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#onTransact(int, android.os.Parcel, android.os.Parcel, int) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#onTransact(int, android.os.Parcel, android.os.Parcel, int) parameter #2: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#removeProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#removeProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#removeStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#removeStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#requestDownloadState(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#requestDownloadState(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#requestUpdateFileServices(int, java.util.List<java.lang.String>) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsDownloadServiceBase#setTempFileRootDirectory(int, String) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#onBind(android.content.Intent): - -MissingNullability: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#onBind(android.content.Intent) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#asBinder(): - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#getPlaybackUri(int, String) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#onTransact(int, android.os.Parcel, android.os.Parcel, int) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#onTransact(int, android.os.Parcel, android.os.Parcel, int) parameter #2: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#requestUpdateStreamingServices(int, java.util.List<java.lang.String>) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#startStreaming(int, String, android.telephony.mbms.StreamingServiceCallback) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#startStreaming(int, String, android.telephony.mbms.StreamingServiceCallback) parameter #2: - -MissingNullability: android.telephony.mbms.vendor.MbmsStreamingServiceBase#stopStreaming(int, String) parameter #1: - -MissingNullability: android.telephony.mbms.vendor.VendorUtils#getAppReceiverFromPackageName(android.content.Context, String): - -MissingNullability: android.telephony.mbms.vendor.VendorUtils#getAppReceiverFromPackageName(android.content.Context, String) parameter #0: - -MissingNullability: android.telephony.mbms.vendor.VendorUtils#getAppReceiverFromPackageName(android.content.Context, String) parameter #1: - + Missing nullability on parameter `apn` in method `setCarrierTestOverride` MissingNullability: android.text.Selection.MemoryTextWatcher#afterTextChanged(android.text.Editable) parameter #0: - + Missing nullability on parameter `s` in method `afterTextChanged` MissingNullability: android.text.Selection.MemoryTextWatcher#beforeTextChanged(CharSequence, int, int, int) parameter #0: - + Missing nullability on parameter `s` in method `beforeTextChanged` MissingNullability: android.text.Selection.MemoryTextWatcher#onTextChanged(CharSequence, int, int, int) parameter #0: - + Missing nullability on parameter `s` in method `onTextChanged` MissingNullability: android.transition.TransitionManager#getTransition(android.transition.Scene): - + Missing nullability on method `getTransition` return MissingNullability: android.transition.TransitionManager#getTransition(android.transition.Scene) parameter #0: - + Missing nullability on parameter `scene` in method `getTransition` MissingNullability: android.util.FeatureFlagUtils#getAllFeatureFlags(): - + Missing nullability on method `getAllFeatureFlags` return MissingNullability: android.util.FeatureFlagUtils#isEnabled(android.content.Context, String) parameter #0: - + Missing nullability on parameter `context` in method `isEnabled` MissingNullability: android.util.FeatureFlagUtils#isEnabled(android.content.Context, String) parameter #1: - + Missing nullability on parameter `feature` in method `isEnabled` MissingNullability: android.util.FeatureFlagUtils#setEnabled(android.content.Context, String, boolean) parameter #0: - + Missing nullability on parameter `context` in method `setEnabled` MissingNullability: android.util.FeatureFlagUtils#setEnabled(android.content.Context, String, boolean) parameter #1: - + Missing nullability on parameter `feature` in method `setEnabled` MissingNullability: android.util.TimeUtils#formatDuration(long): - + Missing nullability on method `formatDuration` return MissingNullability: android.util.proto.EncodedBuffer#dumpBuffers(String) parameter #0: - + Missing nullability on parameter `tag` in method `dumpBuffers` MissingNullability: android.util.proto.EncodedBuffer#dumpByteString(String, String, byte[]) parameter #0: - + Missing nullability on parameter `tag` in method `dumpByteString` MissingNullability: android.util.proto.EncodedBuffer#dumpByteString(String, String, byte[]) parameter #1: - + Missing nullability on parameter `prefix` in method `dumpByteString` MissingNullability: android.util.proto.EncodedBuffer#dumpByteString(String, String, byte[]) parameter #2: - + Missing nullability on parameter `buf` in method `dumpByteString` MissingNullability: android.util.proto.EncodedBuffer#getBytes(int): - + Missing nullability on method `getBytes` return MissingNullability: android.util.proto.EncodedBuffer#getDebugString(): - + Missing nullability on method `getDebugString` return MissingNullability: android.util.proto.EncodedBuffer#writeRawBuffer(byte[]) parameter #0: - + Missing nullability on parameter `val` in method `writeRawBuffer` MissingNullability: android.util.proto.EncodedBuffer#writeRawBuffer(byte[], int, int) parameter #0: - -MissingNullability: android.util.proto.ProtoOutputStream#ProtoOutputStream(java.io.FileDescriptor) parameter #0: - -MissingNullability: android.util.proto.ProtoOutputStream#ProtoOutputStream(java.io.OutputStream) parameter #0: - -MissingNullability: android.util.proto.ProtoOutputStream#dump(String) parameter #0: - -MissingNullability: android.util.proto.ProtoOutputStream#getBytes(): - -MissingNullability: android.util.proto.ProtoOutputStream#write(long, String) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#write(long, byte[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writeBytes(long, byte[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writeObject(long, byte[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedBool(long, boolean[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedDouble(long, double[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedEnum(long, int[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedFixed32(long, int[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedFixed64(long, long[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedFloat(long, float[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedInt32(long, int[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedInt64(long, long[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedSFixed32(long, int[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedSFixed64(long, long[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedSInt32(long, int[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedSInt64(long, long[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedUInt32(long, int[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writePackedUInt64(long, long[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writeRepeatedBytes(long, byte[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writeRepeatedObject(long, byte[]) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writeRepeatedString(long, String) parameter #1: - -MissingNullability: android.util.proto.ProtoOutputStream#writeString(long, String) parameter #1: - + Missing nullability on parameter `val` in method `writeRawBuffer` MissingNullability: android.util.proto.ProtoParseException#ProtoParseException(String) parameter #0: - -MissingNullability: android.util.proto.ProtoStream#FIELD_TYPE_NAMES: - -MissingNullability: android.util.proto.ProtoStream#getFieldCountString(long): - -MissingNullability: android.util.proto.ProtoStream#getFieldIdString(long): - -MissingNullability: android.util.proto.ProtoStream#getFieldTypeString(long): - -MissingNullability: android.util.proto.ProtoStream#getWireTypeString(int): - -MissingNullability: android.util.proto.ProtoStream#token2String(long): - + Missing nullability on parameter `msg` in method `ProtoParseException` MissingNullability: android.util.proto.WireTypeMismatchException#WireTypeMismatchException(String) parameter #0: - + Missing nullability on parameter `msg` in method `WireTypeMismatchException` MissingNullability: android.view.Choreographer#postCallback(int, Runnable, Object) parameter #1: - + Missing nullability on parameter `action` in method `postCallback` MissingNullability: android.view.Choreographer#postCallback(int, Runnable, Object) parameter #2: - + Missing nullability on parameter `token` in method `postCallback` MissingNullability: android.view.Choreographer#postCallbackDelayed(int, Runnable, Object, long) parameter #1: - + Missing nullability on parameter `action` in method `postCallbackDelayed` MissingNullability: android.view.Choreographer#postCallbackDelayed(int, Runnable, Object, long) parameter #2: - + Missing nullability on parameter `token` in method `postCallbackDelayed` MissingNullability: android.view.Choreographer#removeCallbacks(int, Runnable, Object) parameter #1: - + Missing nullability on parameter `action` in method `removeCallbacks` MissingNullability: android.view.Choreographer#removeCallbacks(int, Runnable, Object) parameter #2: - + Missing nullability on parameter `token` in method `removeCallbacks` MissingNullability: android.view.FocusFinder#sort(android.view.View[], int, int, android.view.ViewGroup, boolean) parameter #0: - + Missing nullability on parameter `views` in method `sort` MissingNullability: android.view.FocusFinder#sort(android.view.View[], int, int, android.view.ViewGroup, boolean) parameter #3: - + Missing nullability on parameter `root` in method `sort` MissingNullability: android.view.KeyEvent#actionToString(int): - -MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0: - -MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1: - -MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2: - -MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0: - -MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1: - + Missing nullability on method `actionToString` return MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0: - + Missing nullability on parameter `attrs` in method `relayout` MissingNullability: android.view.View#getTooltipView(): - + Missing nullability on method `getTooltipView` return MissingNullability: android.view.View#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #0: - + Missing nullability on parameter `background` in method `isDefaultFocusHighlightNeeded` MissingNullability: android.view.View#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #1: - + Missing nullability on parameter `foreground` in method `isDefaultFocusHighlightNeeded` MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.concurrent.Callable<java.io.OutputStream>) parameter #0: - + Missing nullability on parameter `tree` in method `startRenderingCommandsCapture` MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.concurrent.Callable<java.io.OutputStream>) parameter #1: - + Missing nullability on parameter `executor` in method `startRenderingCommandsCapture` MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.concurrent.Callable<java.io.OutputStream>) parameter #2: - -MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>) parameter #0: - -MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>) parameter #1: - -MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>) parameter #2: - + Missing nullability on parameter `callback` in method `startRenderingCommandsCapture` MissingNullability: android.view.WindowManager#holdLock(android.os.IBinder, int) parameter #0: - + Missing nullability on parameter `token` in method `holdLock` MissingNullability: android.view.WindowManager.LayoutParams#accessibilityTitle: - -MissingNullability: android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager) parameter #0: - -MissingNullability: android.view.accessibility.AccessibilityNodeInfo#setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger) parameter #0: - + Missing nullability on field `accessibilityTitle` in class `class android.view.WindowManager.LayoutParams` MissingNullability: android.view.accessibility.AccessibilityNodeInfo#writeToParcelNoRecycle(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `parcel` in method `writeToParcelNoRecycle` MissingNullability: android.view.accessibility.AccessibilityWindowInfo#setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger) parameter #0: - -MissingNullability: android.view.contentcapture.ContentCaptureEvent#writeToParcel(android.os.Parcel, int) parameter #0: - + Missing nullability on parameter `counter` in method `setNumInstancesInUseCounter` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#asyncNewChild(int): - + Missing nullability on method `asyncNewChild` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#getAutofillId(): - + Missing nullability on method `getAutofillId` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#getExtras(): - + Missing nullability on method `getExtras` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#getHint(): - + Missing nullability on method `getHint` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#getNode(): - + Missing nullability on method `getNode` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#getTempRect(): - + Missing nullability on method `getTempRect` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#getText(): - + Missing nullability on method `getText` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#newChild(int): - + Missing nullability on method `newChild` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#newHtmlInfoBuilder(String): - + Missing nullability on method `newHtmlInfoBuilder` return MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#newHtmlInfoBuilder(String) parameter #0: - + Missing nullability on parameter `tagName` in method `newHtmlInfoBuilder` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setAutofillHints(String[]) parameter #0: - + Missing nullability on parameter `hints` in method `setAutofillHints` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setAutofillId(android.view.autofill.AutofillId) parameter #0: - + Missing nullability on parameter `id` in method `setAutofillId` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setAutofillId(android.view.autofill.AutofillId, int) parameter #0: - + Missing nullability on parameter `parentId` in method `setAutofillId` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setAutofillOptions(CharSequence[]) parameter #0: - + Missing nullability on parameter `options` in method `setAutofillOptions` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setAutofillValue(android.view.autofill.AutofillValue) parameter #0: - + Missing nullability on parameter `value` in method `setAutofillValue` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setClassName(String) parameter #0: - + Missing nullability on parameter `className` in method `setClassName` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setContentDescription(CharSequence) parameter #0: - + Missing nullability on parameter `contentDescription` in method `setContentDescription` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setHint(CharSequence) parameter #0: - + Missing nullability on parameter `hint` in method `setHint` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setHintIdEntry(String) parameter #0: - + Missing nullability on parameter `entryName` in method `setHintIdEntry` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setHtmlInfo(android.view.ViewStructure.HtmlInfo) parameter #0: - + Missing nullability on parameter `htmlInfo` in method `setHtmlInfo` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setId(int, String, String, String) parameter #1: - + Missing nullability on parameter `packageName` in method `setId` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setId(int, String, String, String) parameter #2: - + Missing nullability on parameter `typeName` in method `setId` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setId(int, String, String, String) parameter #3: - + Missing nullability on parameter `entryName` in method `setId` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setLocaleList(android.os.LocaleList) parameter #0: - + Missing nullability on parameter `localeList` in method `setLocaleList` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setText(CharSequence) parameter #0: - + Missing nullability on parameter `text` in method `setText` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setText(CharSequence, int, int) parameter #0: - + Missing nullability on parameter `text` in method `setText` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setTextLines(int[], int[]) parameter #0: - + Missing nullability on parameter `charOffsets` in method `setTextLines` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setTextLines(int[], int[]) parameter #1: - + Missing nullability on parameter `baselines` in method `setTextLines` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setTransformation(android.graphics.Matrix) parameter #0: - + Missing nullability on parameter `matrix` in method `setTransformation` MissingNullability: android.view.contentcapture.ViewNode.ViewStructureImpl#setWebDomain(String) parameter #0: - + Missing nullability on parameter `domain` in method `setWebDomain` MissingNullability: android.widget.CalendarView#getBoundsForDate(long, android.graphics.Rect) parameter #1: - + Missing nullability on parameter `outBounds` in method `getBoundsForDate` MissingNullability: android.widget.ImageView#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #0: - + Missing nullability on parameter `background` in method `isDefaultFocusHighlightNeeded` MissingNullability: android.widget.ImageView#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #1: - + Missing nullability on parameter `foreground` in method `isDefaultFocusHighlightNeeded` MissingNullability: android.widget.Magnifier#getMagnifierDefaultSize(): - + Missing nullability on method `getMagnifierDefaultSize` return MissingNullability: android.widget.Magnifier#setOnOperationCompleteCallback(android.widget.Magnifier.Callback) parameter #0: - + Missing nullability on parameter `callback` in method `setOnOperationCompleteCallback` MissingNullability: android.widget.NumberPicker#getDisplayedValueForCurrentSelection(): - + Missing nullability on method `getDisplayedValueForCurrentSelection` return MissingNullability: android.widget.PopupMenu#getMenuListView(): - + Missing nullability on method `getMenuListView` return MissingNullability: android.widget.TimePicker#getAmView(): - + Missing nullability on method `getAmView` return MissingNullability: android.widget.TimePicker#getHourView(): - + Missing nullability on method `getHourView` return MissingNullability: android.widget.TimePicker#getMinuteView(): - + Missing nullability on method `getMinuteView` return MissingNullability: android.widget.TimePicker#getPmView(): - + Missing nullability on method `getPmView` return MutableBareField: android.content.AutofillOptions#appDisabledExpiration: - + Bare field appDisabledExpiration must be marked final, or moved behind accessors if mutable MutableBareField: android.content.AutofillOptions#augmentedAutofillEnabled: - + Bare field augmentedAutofillEnabled must be marked final, or moved behind accessors if mutable MutableBareField: android.content.AutofillOptions#disabledActivities: - + Bare field disabledActivities must be marked final, or moved behind accessors if mutable MutableBareField: android.content.AutofillOptions#whitelistedActivitiesForAugmentedAutofill: - + Bare field whitelistedActivitiesForAugmentedAutofill must be marked final, or moved behind accessors if mutable MutableBareField: android.content.pm.UserInfo#convertedFromPreCreated: Bare field convertedFromPreCreated must be marked final, or moved behind accessors if mutable MutableBareField: android.content.pm.UserInfo#creationTime: @@ -2438,588 +796,230 @@ MutableBareField: android.content.pm.UserInfo#serialNumber: MutableBareField: android.content.pm.UserInfo#userType: Bare field userType must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#cache: - + Bare field cache must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#dbName: - + Bare field dbName must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#dbSize: - + Bare field dbSize must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#lookaside: - + Bare field lookaside must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#pageSize: - + Bare field pageSize must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.PagerStats#dbStats: - + Bare field dbStats must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.PagerStats#largestMemAlloc: - + Bare field largestMemAlloc must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.PagerStats#memoryUsed: - + Bare field memoryUsed must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.PagerStats#pageCacheOverflow: - + Bare field pageCacheOverflow must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#broadcastIntentAction: - + Bare field broadcastIntentAction must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#durationMillis: - + Bare field durationMillis must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#numAnimationsRunning: - + Bare field numAnimationsRunning must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#numInstances: - + Bare field numInstances must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#tags: - + Bare field tags must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#violationNumThisLoop: - + Bare field violationNumThisLoop must be marked final, or moved behind accessors if mutable MutableBareField: android.os.StrictMode.ViolationInfo#violationUptimeMillis: - + Bare field violationUptimeMillis must be marked final, or moved behind accessors if mutable NoByteOrShort: android.media.audiofx.AudioEffect#byteArrayToShort(byte[]): - + Should avoid odd sized primitives; use `int` instead of `short` in method android.media.audiofx.AudioEffect.byteArrayToShort(byte[]) NoByteOrShort: android.media.audiofx.AudioEffect#setParameter(int, short) parameter #1: - + Should avoid odd sized primitives; use `int` instead of `short` in parameter value in android.media.audiofx.AudioEffect.setParameter(int param, short value) NoByteOrShort: android.media.audiofx.AudioEffect#shortToByteArray(short) parameter #0: - -NoByteOrShort: android.os.HwBlob#getInt16(long): - -NoByteOrShort: android.os.HwBlob#getInt8(long): - -NoByteOrShort: android.os.HwBlob#putInt16(long, short) parameter #1: - -NoByteOrShort: android.os.HwBlob#putInt8(long, byte) parameter #1: - -NoByteOrShort: android.os.HwParcel#readInt16(): - -NoByteOrShort: android.os.HwParcel#readInt8(): - -NoByteOrShort: android.os.HwParcel#writeInt16(short) parameter #0: - -NoByteOrShort: android.os.HwParcel#writeInt8(byte) parameter #0: - + Should avoid odd sized primitives; use `int` instead of `short` in parameter value in android.media.audiofx.AudioEffect.shortToByteArray(short value) NoByteOrShort: android.util.proto.EncodedBuffer#readRawByte(): - + Should avoid odd sized primitives; use `int` instead of `byte` in method android.util.proto.EncodedBuffer.readRawByte() NoByteOrShort: android.util.proto.EncodedBuffer#writeRawByte(byte) parameter #0: - - - -NoClone: android.net.util.SocketUtils#bindSocketToInterface(java.io.FileDescriptor, String) parameter #0: - -NoClone: android.net.util.SocketUtils#closeSocket(java.io.FileDescriptor) parameter #0: - -NoClone: android.os.NativeHandle#NativeHandle(java.io.FileDescriptor, boolean) parameter #0: - -NoClone: android.os.NativeHandle#getFileDescriptor(): - -NoClone: android.os.ParcelFileDescriptor#getFile(java.io.FileDescriptor) parameter #0: - -NoClone: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: - -NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: - -NoClone: android.util.proto.ProtoOutputStream#ProtoOutputStream(java.io.FileDescriptor) parameter #0: - + Should avoid odd sized primitives; use `int` instead of `byte` in parameter val in android.util.proto.EncodedBuffer.writeRawByte(byte val) NoSettingsProvider: android.provider.Settings.Global#APP_OPS_CONSTANTS: - -NoSettingsProvider: android.provider.Settings.Global#AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: - + New setting keys are not allowed (Field: APP_OPS_CONSTANTS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#AUTOMATIC_POWER_SAVE_MODE: - + New setting keys are not allowed (Field: AUTOMATIC_POWER_SAVE_MODE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#BATTERY_SAVER_CONSTANTS: - + New setting keys are not allowed (Field: BATTERY_SAVER_CONSTANTS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD: - + New setting keys are not allowed (Field: DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#DYNAMIC_POWER_SAVINGS_ENABLED: - + New setting keys are not allowed (Field: DYNAMIC_POWER_SAVINGS_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS: - + New setting keys are not allowed (Field: HIDDEN_API_BLACKLIST_EXEMPTIONS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY: - + New setting keys are not allowed (Field: HIDDEN_API_POLICY); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#HIDE_ERROR_DIALOGS: - -NoSettingsProvider: android.provider.Settings.Global#LOCATION_GLOBAL_KILL_SWITCH: - -NoSettingsProvider: android.provider.Settings.Global#LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST: - + New setting keys are not allowed (Field: HIDE_ERROR_DIALOGS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#LOW_POWER_MODE: - + New setting keys are not allowed (Field: LOW_POWER_MODE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#LOW_POWER_MODE_STICKY: - -NoSettingsProvider: android.provider.Settings.Global#NOTIFICATION_BUBBLES: - + New setting keys are not allowed (Field: LOW_POWER_MODE_STICKY); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Global#OVERLAY_DISPLAY_DEVICES: - -NoSettingsProvider: android.provider.Settings.Global#TETHER_OFFLOAD_DISABLED: - -NoSettingsProvider: android.provider.Settings.Global#USE_OPEN_WIFI_PACKAGE: - + New setting keys are not allowed (Field: OVERLAY_DISPLAY_DEVICES); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED: - + New setting keys are not allowed (Field: ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY: - + New setting keys are not allowed (Field: ACCESSIBILITY_MAGNIFICATION_CAPABILITY); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE: - -NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_ALL: - -NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN: - -NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW: - + New setting keys are not allowed (Field: ACCESSIBILITY_MAGNIFICATION_MODE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE: - -NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_FEATURE_FIELD_CLASSIFICATION: - + New setting keys are not allowed (Field: ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_SERVICE: - -NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT: - -NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE: - -NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE: - -NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_USER_DATA_MAX_VALUE_LENGTH: - -NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_USER_DATA_MIN_VALUE_LENGTH: - + New setting keys are not allowed (Field: AUTOFILL_SERVICE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#CONTENT_CAPTURE_ENABLED: - + New setting keys are not allowed (Field: CONTENT_CAPTURE_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#DISABLED_PRINT_SERVICES: - -NoSettingsProvider: android.provider.Settings.Secure#DOZE_ALWAYS_ON: - + New setting keys are not allowed (Field: DISABLED_PRINT_SERVICES); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#ENABLED_VR_LISTENERS: - + New setting keys are not allowed (Field: ENABLED_VR_LISTENERS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#IMMERSIVE_MODE_CONFIRMATIONS: - -NoSettingsProvider: android.provider.Settings.Secure#LOCATION_ACCESS_CHECK_DELAY_MILLIS: - -NoSettingsProvider: android.provider.Settings.Secure#LOCATION_ACCESS_CHECK_INTERVAL_MILLIS: - -NoSettingsProvider: android.provider.Settings.Secure#LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS: - -NoSettingsProvider: android.provider.Settings.Secure#LOCK_SCREEN_SHOW_NOTIFICATIONS: - -NoSettingsProvider: android.provider.Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT: - + New setting keys are not allowed (Field: IMMERSIVE_MODE_CONFIRMATIONS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#NOTIFICATION_BADGING: - + New setting keys are not allowed (Field: NOTIFICATION_BADGING); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#POWER_MENU_LOCKED_SHOW_CONTENT: - + New setting keys are not allowed (Field: POWER_MENU_LOCKED_SHOW_CONTENT); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#SYNC_PARENT_SOUNDS: - -NoSettingsProvider: android.provider.Settings.Secure#USER_SETUP_COMPLETE: - + New setting keys are not allowed (Field: SYNC_PARENT_SOUNDS); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#VOICE_INTERACTION_SERVICE: - + New setting keys are not allowed (Field: VOICE_INTERACTION_SERVICE); use getters/setters in relevant manager class -NotCloseable: android.app.prediction.AppPredictor: - -NotCloseable: android.net.EthernetManager.TetheredInterfaceRequest: - -NotCloseable: android.os.HwParcel: - -NotCloseable: android.telephony.ims.stub.ImsUtImplBase: - - - -NullableCollection: android.os.UserManager#createProfileForUser(String, String, int, int, String[]) parameter #4: - Type of parameter disallowedPackages in android.os.UserManager.createProfileForUser(String name, String userType, int flags, int userId, String[] disallowedPackages) is a nullable collection (`java.lang.String[]`); must be non-null - - -OnNameExpected: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.PrintWriter, String[]): - -OnNameExpected: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): - OnNameExpected: android.service.notification.ConditionProviderService#isBound(): - -OnNameExpected: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context): - -OnNameExpected: android.service.quicksettings.TileService#isQuickSettingsSupported(): - + If implemented by developer, should follow the on<Something> style; otherwise consider marking final OnNameExpected: android.service.watchdog.ExplicitHealthCheckService#setCallback(android.os.RemoteCallback): - -OnNameExpected: android.telephony.ims.ImsService#createMmTelFeature(int): - -OnNameExpected: android.telephony.ims.ImsService#createRcsFeature(int): - -OnNameExpected: android.telephony.ims.ImsService#disableIms(int): - -OnNameExpected: android.telephony.ims.ImsService#enableIms(int): - -OnNameExpected: android.telephony.ims.ImsService#getConfig(int): - -OnNameExpected: android.telephony.ims.ImsService#getRegistration(int): - -OnNameExpected: android.telephony.ims.ImsService#querySupportedImsFeatures(): - -OnNameExpected: android.telephony.ims.ImsService#readyForFeatureCreation(): - -OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#dispose(int): - -OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int): - -OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#startGroupCall(int, long, java.util.List<java.lang.Integer>, java.util.List<java.lang.Integer>, android.telephony.mbms.GroupCallCallback): - -OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#stopGroupCall(int, long): - -OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#updateGroupCall(int, long, java.util.List<java.lang.Integer>, java.util.List<java.lang.Integer>): - - - -OptionalBuilderConstructorArgument: android.app.prediction.AppTargetEvent.Builder#Builder(android.app.prediction.AppTarget, int) parameter #0: - -OptionalBuilderConstructorArgument: android.net.CaptivePortalData.Builder#Builder(android.net.CaptivePortalData) parameter #0: - -OptionalBuilderConstructorArgument: android.os.VibrationAttributes.Builder#Builder(android.media.AudioAttributes, android.os.VibrationEffect) parameter #1: - + If implemented by developer, should follow the on<Something> style; otherwise consider marking final PackageLayering: android.util.FeatureFlagUtils: - + Method parameter type `android.content.Context` violates package layering: nothing in `package android.util` should depend on `package android.content` -ParcelConstructor: android.os.IncidentManager.IncidentReport#IncidentReport(android.os.Parcel): - -ParcelConstructor: android.os.IncidentReportArgs#IncidentReportArgs(android.os.Parcel): - ParcelConstructor: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel): - + Parcelable inflation is exposed through CREATOR, not raw constructors, in android.os.StrictMode.ViolationInfo ParcelConstructor: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel): - -ParcelConstructor: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel): - + Parcelable inflation is exposed through CREATOR, not raw constructors, in android.os.health.HealthStatsParceler ParcelCreator: android.app.WindowConfiguration: - -ParcelCreator: android.net.metrics.ApfProgramEvent: - -ParcelCreator: android.net.metrics.ApfStats: - -ParcelCreator: android.net.metrics.DhcpClientEvent: - -ParcelCreator: android.net.metrics.DhcpErrorEvent: - -ParcelCreator: android.net.metrics.IpConnectivityLog.Event: - -ParcelCreator: android.net.metrics.IpManagerEvent: - -ParcelCreator: android.net.metrics.IpReachabilityEvent: - -ParcelCreator: android.net.metrics.NetworkEvent: - -ParcelCreator: android.net.metrics.RaEvent: - -ParcelCreator: android.net.metrics.ValidationProbeEvent: - + Parcelable requires a `CREATOR` field; missing in android.app.WindowConfiguration ParcelCreator: android.service.autofill.InternalOnClickAction: - + Parcelable requires a `CREATOR` field; missing in android.service.autofill.InternalOnClickAction ParcelCreator: android.service.autofill.InternalSanitizer: - + Parcelable requires a `CREATOR` field; missing in android.service.autofill.InternalSanitizer ParcelCreator: android.service.autofill.InternalTransformation: - + Parcelable requires a `CREATOR` field; missing in android.service.autofill.InternalTransformation ParcelCreator: android.service.autofill.InternalValidator: - + Parcelable requires a `CREATOR` field; missing in android.service.autofill.InternalValidator ParcelNotFinal: android.app.WindowConfiguration: - + Parcelable classes must be final: android.app.WindowConfiguration is not final ParcelNotFinal: android.content.pm.UserInfo: Parcelable classes must be final: android.content.pm.UserInfo is not final -ParcelNotFinal: android.net.metrics.IpConnectivityLog.Event: - -ParcelNotFinal: android.os.IncidentManager.IncidentReport: - ParcelNotFinal: android.os.health.HealthStatsParceler: - + Parcelable classes must be final: android.os.health.HealthStatsParceler is not final ParcelNotFinal: android.service.autofill.InternalOnClickAction: - + Parcelable classes must be final: android.service.autofill.InternalOnClickAction is not final ParcelNotFinal: android.service.autofill.InternalSanitizer: - + Parcelable classes must be final: android.service.autofill.InternalSanitizer is not final ParcelNotFinal: android.service.autofill.InternalTransformation: - + Parcelable classes must be final: android.service.autofill.InternalTransformation is not final ParcelNotFinal: android.service.autofill.InternalValidator: - + Parcelable classes must be final: android.service.autofill.InternalValidator is not final ProtectedMember: android.app.AppDetailsActivity#onCreate(android.os.Bundle): - -ProtectedMember: android.os.VibrationEffect#scale(int, float, int): - -ProtectedMember: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): - -ProtectedMember: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.PrintWriter, String[]): - -ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): - -ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context): - -ProtectedMember: android.util.proto.ProtoStream#FIELD_TYPE_NAMES: - + Protected methods not allowed; must be public: method android.app.AppDetailsActivity.onCreate(android.os.Bundle)} ProtectedMember: android.view.View#resetResolvedDrawables(): - + Protected methods not allowed; must be public: method android.view.View.resetResolvedDrawables()} ProtectedMember: android.view.ViewGroup#resetResolvedDrawables(): - + Protected methods not allowed; must be public: method android.view.ViewGroup.resetResolvedDrawables()} -RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase: - -RawAidl: android.telephony.mbms.vendor.MbmsStreamingServiceBase: - +RethrowRemoteException: android.app.ActivityManager#resumeAppSwitches(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) -RethrowRemoteException: android.app.ActivityManager#resumeAppSwitches(): - -RethrowRemoteException: android.os.HwBinder#getService(String, String): - -RethrowRemoteException: android.os.HwBinder#getService(String, String, boolean): - -RethrowRemoteException: android.os.HwBinder#onTransact(int, android.os.HwParcel, android.os.HwParcel, int): - -RethrowRemoteException: android.os.HwBinder#registerService(String): - -RethrowRemoteException: android.os.HwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int): - -RethrowRemoteException: android.os.IHwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int): - -RethrowRemoteException: android.telephony.ims.ImsService#onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#cancelDownload(android.telephony.mbms.DownloadRequest): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#dispose(int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#download(android.telephony.mbms.DownloadRequest): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#listPendingDownloads(int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#onTransact(int, android.os.Parcel, android.os.Parcel, int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#removeProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#removeStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#requestDownloadState(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#requestUpdateFileServices(int, java.util.List<java.lang.String>): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#resetDownloadKnowledge(android.telephony.mbms.DownloadRequest): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#setTempFileRootDirectory(int, String): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#dispose(int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#dispose(int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#getPlaybackUri(int, String): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#onTransact(int, android.os.Parcel, android.os.Parcel, int): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#requestUpdateStreamingServices(int, java.util.List<java.lang.String>): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#startStreaming(int, String, android.telephony.mbms.StreamingServiceCallback): - -RethrowRemoteException: android.telephony.mbms.vendor.MbmsStreamingServiceBase#stopStreaming(int, String): - - - -SamShouldBeLast: android.app.ActivityManager#addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int): - SamShouldBeLast: android.database.sqlite.SQLiteDebug#dump(android.util.Printer, String[]): - + SAM-compatible parameters (such as parameter 1, "printer", in android.database.sqlite.SQLiteDebug.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]): - -SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper): - -SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener): - -SamShouldBeLast: android.os.BugreportManager#startBugreport(android.os.ParcelFileDescriptor, android.os.ParcelFileDescriptor, android.os.BugreportParams, java.util.concurrent.Executor, android.os.BugreportManager.BugreportCallback): - -SamShouldBeLast: android.os.IHwBinder#linkToDeath(android.os.IHwBinder.DeathRecipient, long): - + SAM-compatible parameters (such as parameter 1, "factory", in android.database.sqlite.SQLiteDirectCursorDriver.query) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.os.StrictMode.ViolationInfo#dump(android.util.Printer, String): - + SAM-compatible parameters (such as parameter 1, "pw", in android.os.StrictMode.ViolationInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler): - + SAM-compatible parameters (such as parameter 3, "callback", in android.permission.PermissionControllerManager.countPermissionApps) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler): - -SamShouldBeLast: android.permission.PermissionControllerManager#revokeRuntimePermissions(java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback): - + SAM-compatible parameters (such as parameter 2, "callback", in android.permission.PermissionControllerManager.getAppPermissions) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.service.autofill.CharSequenceTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int): - + SAM-compatible parameters (such as parameter 1, "finder", in android.service.autofill.CharSequenceTransformation.apply) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.service.autofill.DateTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int): - + SAM-compatible parameters (such as parameter 1, "finder", in android.service.autofill.DateTransformation.apply) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.service.autofill.ImageTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int): - + SAM-compatible parameters (such as parameter 1, "finder", in android.service.autofill.ImageTransformation.apply) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.service.autofill.InternalTransformation#batchApply(android.service.autofill.ValueFinder, android.widget.RemoteViews, java.util.ArrayList<android.util.Pair<java.lang.Integer,android.service.autofill.InternalTransformation>>): - -SamShouldBeLast: android.telephony.ims.ImsMmTelManager#getFeatureState(java.util.function.Consumer<java.lang.Integer>, java.util.concurrent.Executor): - + SAM-compatible parameters (such as parameter 1, "finder", in android.service.autofill.InternalTransformation.batchApply) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.Choreographer#postCallback(int, Runnable, Object): - + SAM-compatible parameters (such as parameter 2, "action", in android.view.Choreographer.postCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.Choreographer#postCallbackDelayed(int, Runnable, Object, long): - + SAM-compatible parameters (such as parameter 2, "action", in android.view.Choreographer.postCallbackDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.Choreographer#removeCallbacks(int, Runnable, Object): - -SamShouldBeLast: android.view.ViewDebug#startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>): - -SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, android.os.Handler): - - - -ServiceName: android.Manifest.permission#BIND_CELL_BROADCAST_SERVICE: - -ServiceName: android.app.AppOpsManager#OPSTR_BIND_ACCESSIBILITY_SERVICE: - -ServiceName: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE: - -ServiceName: android.provider.Settings.Secure#AUTOFILL_SERVICE: - -ServiceName: android.provider.Settings.Secure#VOICE_INTERACTION_SERVICE: - - - -SetterReturnsThis: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener): - -SetterReturnsThis: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener): - - - -StartWithLower: android.content.pm.PackageManager#BINDER(): - Method name must start with lowercase char: BINDER - - -StaticFinalBuilder: android.content.integrity.RuleSet.Builder: - -StaticFinalBuilder: android.hardware.display.BrightnessConfiguration.Builder: - -StaticFinalBuilder: android.media.audiopolicy.AudioMix.Builder: - -StaticFinalBuilder: android.media.audiopolicy.AudioMixingRule.Builder: - -StaticFinalBuilder: android.media.audiopolicy.AudioPolicy.Builder: - -StaticFinalBuilder: android.net.CaptivePortalData.Builder: - -StaticFinalBuilder: android.net.TetheringManager.TetheringRequest.Builder: - -StaticFinalBuilder: android.telephony.ims.stub.ImsFeatureConfiguration.Builder: - + SAM-compatible parameters (such as parameter 2, "action", in android.view.Choreographer.removeCallbacks) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions StaticUtils: android.os.health.HealthKeys: - + Fully-static utility classes must not have constructor StaticUtils: android.service.autofill.InternalTransformation: - -StaticUtils: android.telephony.mbms.vendor.VendorUtils: - + Fully-static utility classes must not have constructor StaticUtils: android.util.FeatureFlagUtils: - -StaticUtils: android.util.proto.ProtoStream: - + Fully-static utility classes must not have constructor StreamFiles: android.os.Environment#buildPath(java.io.File, java.lang.String...): - + Methods accepting `File` should also accept `FileDescriptor` or streams: method android.os.Environment.buildPath(java.io.File,java.lang.String...) StreamFiles: android.os.FileUtils#contains(java.io.File, java.io.File): - -StreamFiles: android.provider.MediaStore#scanFile(android.content.Context, java.io.File): - -StreamFiles: android.provider.MediaStore#scanFileFromShell(android.content.Context, java.io.File): - -StreamFiles: android.provider.MediaStore#scanVolume(android.content.Context, java.io.File): - + Methods accepting `File` should also accept `FileDescriptor` or streams: method android.os.FileUtils.contains(java.io.File,java.io.File) UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getKeyphraseMetadata(String, java.util.Locale) parameter #1: - + Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale` UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale) parameter #2: - + Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale` UseIcu: android.hardware.soundtrigger.KeyphraseMetadata#supportsLocale(java.util.Locale) parameter #0: - -UseIcu: android.hardware.soundtrigger.SoundTrigger.Keyphrase#Keyphrase(int, int, java.util.Locale, String, int[]) parameter #2: - -UseIcu: android.hardware.soundtrigger.SoundTrigger.Keyphrase#getLocale(): - - - -UseParcelFileDescriptor: android.util.proto.ProtoOutputStream#ProtoOutputStream(java.io.FileDescriptor) parameter #0: - + Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale` -UserHandle: android.app.ActivityManager#switchUser(android.os.UserHandle): - UserHandle: android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts(android.os.UserHandle): - + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added UserHandle: android.app.usage.StorageStatsManager#queryCratesForPackage(java.util.UUID, String, android.os.UserHandle): - + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added UserHandle: android.app.usage.StorageStatsManager#queryCratesForUser(java.util.UUID, android.os.UserHandle): - -UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle): - -UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociatedForWifiConnection(String, android.net.MacAddress, android.os.UserHandle): - + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added UserHandle: android.content.pm.PackageManager#getInstallReason(String, android.os.UserHandle): - -UserHandle: android.content.pm.PackageManager#getPermissionFlags(String, String, android.os.UserHandle): - -UserHandle: android.content.pm.PackageManager#grantRuntimePermission(String, String, android.os.UserHandle): - -UserHandle: android.content.pm.PackageManager#revokeRuntimePermission(String, String, android.os.UserHandle): - -UserHandle: android.content.pm.PackageManager#revokeRuntimePermission(String, String, android.os.UserHandle, String): - -UserHandle: android.content.pm.PackageManager#updatePermissionFlags(String, String, int, int, android.os.UserHandle): - -UserHandle: android.location.LocationManager#setLocationEnabledForUser(boolean, android.os.UserHandle): - -UserHandle: android.permission.PermissionControllerManager#applyStagedRuntimePermissionBackup(String, android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Boolean>): - -UserHandle: android.permission.PermissionControllerManager#getRuntimePermissionBackup(android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<byte[]>): - -UserHandle: android.permission.PermissionControllerManager#stageAndApplyRuntimePermissionsBackup(byte[], android.os.UserHandle): - -UserHandle: android.telecom.TelecomManager#getDefaultDialerPackage(android.os.UserHandle): - + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added UserHandleName: android.content.AutofillOptions: - + Classes holding a set of parameters should be called `FooParams`, was `AutofillOptions` UserHandleName: android.content.ContentCaptureOptions: - -UserHandleName: android.os.IncidentReportArgs: - -UserHandleName: android.provider.MediaStore#deleteContributedMedia(android.content.Context, String, android.os.UserHandle): - -UserHandleName: android.provider.MediaStore#getContributedMediaSize(android.content.Context, String, android.os.UserHandle): - - - -VisiblySynchronized: PsiClassObjectAccessExpression: - -VisiblySynchronized: PsiThisExpression: - -VisiblySynchronized: android.app.ActivityManager#addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int): - -VisiblySynchronized: android.app.ActivityManager#removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener): - -VisiblySynchronized: android.content.ContentProviderClient#setDetectNotResponding(long): - + Classes holding a set of parameters should be called `FooParams`, was `ContentCaptureOptions` + + +VisiblySynchronized: PsiThisExpression:this: + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths() VisiblySynchronized: android.content.res.AssetManager#getApkPaths(): - + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths() VisiblySynchronized: android.content.res.AssetManager#getLastResourceResolution(): - + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getLastResourceResolution() VisiblySynchronized: android.content.res.AssetManager#getOverlayablesToString(String): - + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getOverlayablesToString(String) VisiblySynchronized: android.content.res.AssetManager#setResourceResolutionLoggingEnabled(boolean): - + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.setResourceResolutionLoggingEnabled(boolean) VisiblySynchronized: android.os.MessageQueue#removeSyncBarrier(int): - + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.os.MessageQueue.removeSyncBarrier(int) 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..1c1f58a19abc 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,47 @@ ] }, { - "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" - } - ] } + ], + "platinum-postsubmit": [ + { + "name": "CtsIncrementalInstallHostTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.SplitTests" + }, + { + "include-filter": "android.appsecurity.cts.EphemeralTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsContentTestCases", + "options":[ + { + "include-filter": "android.content.cts.IntentFilterTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } ] } 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..20a2bdf3b109 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 @@ -9696,6 +9682,26 @@ public final class Settings { public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled"; /** + * Whether or not active unlock triggers on wake. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_WAKE = "active_unlock_on_wake"; + + /** + * Whether or not active unlock triggers on unlock intent. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT = + "active_unlock_on_unlock_intent"; + + /** + * Whether or not active unlock triggers on biometric failure. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL = + "active_unlock_on_biometric_fail"; + + /** * Whether the assist gesture should be enabled. * * @hide @@ -16966,6 +16972,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/GameSession.java b/core/java/android/service/games/GameSession.java index 01152943efe3..e8d53d351795 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -25,7 +25,6 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.ActivityTaskManager; import android.app.Instrumentation; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -511,14 +510,11 @@ public abstract class GameSession { callback.onActivityResult(result.getResultCode(), result.getData()); }, executor); - final Intent trampolineIntent = new Intent(); - trampolineIntent.setComponent( - new ComponentName( - "android", "android.service.games.GameSessionTrampolineActivity")); - trampolineIntent.putExtra(GameSessionTrampolineActivity.INTENT_KEY, intent); - trampolineIntent.putExtra(GameSessionTrampolineActivity.OPTIONS_KEY, options); - trampolineIntent.putExtra( - GameSessionTrampolineActivity.FUTURE_KEY, future); + final Intent trampolineIntent = + GameSessionTrampolineActivity.createIntent( + intent, + options, + future); try { int result = ActivityTaskManager.getService().startActivityFromGameSession( diff --git a/core/java/android/service/games/GameSessionActivityResult.java b/core/java/android/service/games/GameSessionActivityResult.java index a2ec6ada010c..c8099e6e5eff 100644 --- a/core/java/android/service/games/GameSessionActivityResult.java +++ b/core/java/android/service/games/GameSessionActivityResult.java @@ -22,8 +22,12 @@ import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; -final class GameSessionActivityResult implements Parcelable { + +/** @hide */ +@VisibleForTesting +public final class GameSessionActivityResult implements Parcelable { public static final Creator<GameSessionActivityResult> CREATOR = new Creator<GameSessionActivityResult>() { @@ -44,17 +48,17 @@ final class GameSessionActivityResult implements Parcelable { @Nullable private final Intent mData; - GameSessionActivityResult(int resultCode, @Nullable Intent data) { + public GameSessionActivityResult(int resultCode, @Nullable Intent data) { mResultCode = resultCode; mData = data; } - int getResultCode() { + public int getResultCode() { return mResultCode; } @Nullable - Intent getData() { + public Intent getData() { return mData; } diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java index df5bad5c53b2..52c8ec3d4018 100644 --- a/core/java/android/service/games/GameSessionService.java +++ b/core/java/android/service/games/GameSessionService.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; import android.os.Binder; @@ -28,6 +29,7 @@ import android.os.Handler; import android.os.IBinder; import android.view.Display; import android.view.SurfaceControlViewHost; +import android.view.WindowManager; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; @@ -117,13 +119,18 @@ public abstract class GameSessionService extends Service { } IBinder hostToken = new Binder(); + + // Use a WindowContext so that views attached to the SurfaceControlViewHost will receive + // configuration changes (rather than always perceiving the global configuration). + final Context windowContext = createWindowContext(display, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, /*options=*/ null); SurfaceControlViewHost surfaceControlViewHost = - new SurfaceControlViewHost(this, display, hostToken); + new SurfaceControlViewHost(windowContext, display, hostToken); gameSession.attach( gameSessionController, createGameSessionRequest.getTaskId(), - this, + windowContext, surfaceControlViewHost, gameSessionViewHostConfiguration.mWidthPx, gameSessionViewHostConfiguration.mHeightPx); diff --git a/core/java/android/service/games/GameSessionTrampolineActivity.java b/core/java/android/service/games/GameSessionTrampolineActivity.java index 3d97d0f59b33..b23791842284 100644 --- a/core/java/android/service/games/GameSessionTrampolineActivity.java +++ b/core/java/android/service/games/GameSessionTrampolineActivity.java @@ -16,12 +16,15 @@ package android.service.games; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; +import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import java.util.concurrent.Executor; @@ -35,6 +38,7 @@ import java.util.concurrent.Executor; * * @hide */ +@VisibleForTesting public final class GameSessionTrampolineActivity extends Activity { private static final String TAG = "GameSessionTrampoline"; private static final int REQUEST_CODE = 1; @@ -42,11 +46,52 @@ public final class GameSessionTrampolineActivity extends Activity { static final String FUTURE_KEY = "GameSessionTrampolineActivity.future"; static final String INTENT_KEY = "GameSessionTrampolineActivity.intent"; static final String OPTIONS_KEY = "GameSessionTrampolineActivity.options"; + private static final String HAS_LAUNCHED_INTENT_KEY = + "GameSessionTrampolineActivity.hasLaunchedIntent"; + private boolean mHasLaunchedIntent = false; + + /** + * Create an {@link Intent} for the {@link GameSessionTrampolineActivity} with the given + * parameters. + * + * @param targetIntent the forwarded {@link Intent} that is associated with the Activity that + * will be launched by the {@link GameSessionTrampolineActivity}. + * @param options Activity options. See {@link #startActivity(Intent, Bundle)}. + * @param resultFuture the {@link AndroidFuture} that will complete with the activity results of + * {@code targetIntent} launched. + * @return the Intent that will launch the {@link GameSessionTrampolineActivity} with the given + * parameters. + * @hide + */ + @VisibleForTesting + public static Intent createIntent( + @NonNull Intent targetIntent, + @Nullable Bundle options, + @NonNull AndroidFuture<GameSessionActivityResult> resultFuture) { + final Intent trampolineIntent = new Intent(); + trampolineIntent.setComponent( + new ComponentName( + "android", "android.service.games.GameSessionTrampolineActivity")); + trampolineIntent.putExtra(INTENT_KEY, targetIntent); + trampolineIntent.putExtra(OPTIONS_KEY, options); + trampolineIntent.putExtra(FUTURE_KEY, resultFuture); + + return trampolineIntent; + } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mHasLaunchedIntent = savedInstanceState.getBoolean(HAS_LAUNCHED_INTENT_KEY); + } + + if (mHasLaunchedIntent) { + return; + } + mHasLaunchedIntent = true; + try { startActivityAsCaller( getIntent().getParcelableExtra(INTENT_KEY), @@ -60,10 +105,17 @@ public final class GameSessionTrampolineActivity extends Activity { FUTURE_KEY); future.completeExceptionally(e); finish(); + overridePendingTransition(0, 0); } } @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(HAS_LAUNCHED_INTENT_KEY, mHasLaunchedIntent); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != REQUEST_CODE) { // Something went very wrong if we hit this code path, and we should bail. @@ -74,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/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 4bbfbc2e717d..b783f6b8fd51 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1095,7 +1095,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall if (!mWindowVisible) { mWindowVisible = true; if (mUiEnabled) { - mWindow.show(); + showWindow(); } } if (showCallback != null) { @@ -1284,9 +1284,25 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } } + void showWindow() { + if (mWindow != null) { + mWindow.show(); + try { + mSystemService.setSessionWindowVisible(mToken, true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to notify session window shown", e); + } + } + } + void ensureWindowHidden() { if (mWindow != null) { mWindow.hide(); + try { + mSystemService.setSessionWindowVisible(mToken, false); + } catch (RemoteException e) { + Log.w(TAG, "Failed to notify session window hidden", e); + } } } @@ -1377,7 +1393,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall if (mWindowVisible) { if (enabled) { ensureWindowAdded(); - mWindow.show(); + showWindow(); } else { ensureWindowHidden(); } 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/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 4fdea3b006dc..332e97c8bcf5 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -65,6 +65,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void onWindowFocusGained(boolean hasViewFocus) { super.onWindowFocusGained(hasViewFocus); getImm().registerImeConsumer(this); + if (isRequestedVisible() && getControl() == null) { + mIsRequestedVisibleAwaitingControl = true; + } } @Override @@ -149,14 +152,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { } @Override - public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, + public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, int[] hideTypes) { - super.setControl(control, showTypes, hideTypes); - // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME: - // 1) Already requested show IME, in the meantime of WM callback the control but got null - // control when relayout comes first - // 2) Make sure no regression on some implicit request IME visibility calls (e.g. - // toggleSoftInput) + if (!super.setControl(control, showTypes, hideTypes)) { + return false; + } if (control == null && !mIsRequestedVisibleAwaitingControl) { hide(); removeSurface(); @@ -164,6 +164,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { if (control != null) { mIsRequestedVisibleAwaitingControl = false; } + return true; } @Override 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/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 4d9033df89e1..d6b75b94b19a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -122,8 +122,9 @@ public class InsetsSourceConsumer { * animation should be run after setting the control. * @param hideTypes An integer array with a single entry that determines which types a hide * animation should be run after setting the control. + * @return Whether the control has changed from the server */ - public void setControl(@Nullable InsetsSourceControl control, + public boolean setControl(@Nullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes) { if (mType == ITYPE_IME) { ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl", @@ -134,7 +135,7 @@ public class InsetsSourceConsumer { mSourceControl.release(SurfaceControl::release); mSourceControl = control; } - return; + return false; } SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; @@ -201,6 +202,7 @@ public class InsetsSourceConsumer { if (lastControl != null) { lastControl.release(SurfaceControl::release); } + return true; } @VisibleForTesting 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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b5bbc7537391..6c4933e07b90 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -785,12 +785,14 @@ public final class SurfaceControl implements Parcelable { private final HardwareBuffer mHardwareBuffer; private final ColorSpace mColorSpace; private final boolean mContainsSecureLayers; + private final boolean mContainsHdrLayers; public ScreenshotHardwareBuffer(HardwareBuffer hardwareBuffer, ColorSpace colorSpace, - boolean containsSecureLayers) { + boolean containsSecureLayers, boolean containsHdrLayers) { mHardwareBuffer = hardwareBuffer; mColorSpace = colorSpace; mContainsSecureLayers = containsSecureLayers; + mContainsHdrLayers = containsHdrLayers; } /** @@ -798,13 +800,15 @@ public final class SurfaceControl implements Parcelable { * @param hardwareBuffer The existing HardwareBuffer object * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} * @param containsSecureLayers Indicates whether this graphic buffer contains captured - * contents - * of secure layers, in which case the screenshot should not be persisted. + * contents of secure layers, in which case the screenshot + * should not be persisted. + * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. */ private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer, - int namedColorSpace, boolean containsSecureLayers) { + int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) { ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]); - return new ScreenshotHardwareBuffer(hardwareBuffer, colorSpace, containsSecureLayers); + return new ScreenshotHardwareBuffer( + hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers); } public ColorSpace getColorSpace() { @@ -818,6 +822,14 @@ public final class SurfaceControl implements Parcelable { public boolean containsSecureLayers() { return mContainsSecureLayers; } + /** + * Returns whether the screenshot contains at least one HDR layer. + * This information may be useful for informing the display whether this screenshot + * is allowed to be dimmed to SDR white. + */ + public boolean containsHdrLayers() { + return mContainsHdrLayers; + } /** * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cf5727ea1342..62d0d37da84d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12066,8 +12066,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>In multiple-screen scenarios, if the surface spans multiple screens, * the coordinate space of the surface also spans multiple screens. * - * <p>After the method returns, the argument array contains the x- and - * y-coordinates of the view relative to the view's left and top edges, + * <p>After the method returns, the argument array contains the x and y + * coordinates of the view relative to the view's left and top edges, * respectively. * * @param location A two-element integer array in which the view coordinates @@ -18743,18 +18743,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * If some part of this view is not clipped by any of its parents, then - * return that area in r in global (root) coordinates. To convert r to local - * coordinates (without taking possible View rotations into account), offset - * it by -globalOffset (e.g. r.offset(-globalOffset.x, -globalOffset.y)). - * If the view is completely clipped or translated out, return false. + * Sets {@code r} to the coordinates of the non-clipped area of this view in + * the coordinate space of the view's root view. Sets {@code globalOffset} + * to the offset of the view's x and y coordinates from the coordinate space + * origin, which is the top left corner of the root view irrespective of + * screen decorations and system UI elements. * - * @param r If true is returned, r holds the global coordinates of the - * visible portion of this view. - * @param globalOffset If true is returned, globalOffset holds the dx,dy - * between this view and its root. globalOffet may be null. - * @return true if r is non-empty (i.e. part of the view is visible at the - * root level. + * <p>To convert {@code r} to coordinates relative to the top left corner of + * this view (without taking view rotations into account), offset {@code r} + * by the inverse values of + * {@code globalOffset}—{@code r.offset(-globalOffset.x, + * -globalOffset.y)}—which is equivalent to calling + * {@link #getLocalVisibleRect(Rect) getLocalVisibleRect(Rect)}. + * + * <p><b>Note:</b> Do not use this method to determine the size of a window + * in multi-window mode; use + * {@link WindowManager#getCurrentWindowMetrics()}. + * + * @param r If the method returns true, contains the coordinates of the + * visible portion of this view in the coordinate space of the view's + * root view. If the method returns false, the contents of {@code r} + * are undefined. + * @param globalOffset If the method returns true, contains the offset of + * the x and y coordinates of this view from the top left corner of the + * view's root view. If the method returns false, the contents of + * {@code globalOffset} are undefined. The argument can be null (see + * {@link #getGlobalVisibleRect(Rect) getGlobalVisibleRect(Rect)}. + * @return true if at least part of the view is visible within the root + * view; false if the view is completely clipped or translated out of + * the visible area of the root view. + * + * @see #getLocalVisibleRect(Rect) */ public boolean getGlobalVisibleRect(Rect r, Point globalOffset) { int width = mRight - mLeft; @@ -18769,10 +18788,48 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } + /** + * Sets {@code r} to the coordinates of the non-clipped area of this view in + * the coordinate space of the view's root view. + * + * <p>See {@link #getGlobalVisibleRect(Rect, Point) + * getGlobalVisibleRect(Rect, Point)} for more information. + * + * @param r If the method returns true, contains the coordinates of the + * visible portion of this view in the coordinate space of the view's + * root view. If the method returns false, the contents of {@code r} + * are undefined. + * @return true if at least part of the view is visible within the root + * view; otherwise false. + */ public final boolean getGlobalVisibleRect(Rect r) { return getGlobalVisibleRect(r, null); } + /** + * Sets {@code r} to the coordinates of the non-clipped area of this view + * relative to the top left corner of the view. + * + * <p>If the view is clipped on the left or top, the left and top + * coordinates are offset from 0 by the clipped amount. For example, if the + * view is off screen 50px on the left and 30px at the top, the left and top + * coordinates are 50 and 30 respectively. + * + * <p>If the view is clipped on the right or bottom, the right and bottom + * coordinates are reduced by the clipped amount. For example, if the view + * is off screen 40px on the right and 20px at the bottom, the right + * coordinate is the view width - 40, and the bottom coordinate is the view + * height - 20. + * + * @param r If the method returns true, contains the coordinates of the + * visible portion of this view relative to the top left corner of the + * view. If the method returns false, the contents of {@code r} are + * undefined. + * @return true if at least part of the view is visible; false if the view + * is completely clipped or translated out of the visible area. + * + * @see #getGlobalVisibleRect(Rect, Point) + */ public final boolean getLocalVisibleRect(Rect r) { final Point offset = mAttachInfo != null ? mAttachInfo.mPoint : new Point(); if (getGlobalVisibleRect(r, offset)) { @@ -25606,8 +25663,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * multiple-screen environment, the coordinate space includes only the * screen on which the app is running. * - * <p>After the method returns, the argument array contains the x- and - * y-coordinates of the view relative to the view's left and top edges, + * <p>After the method returns, the argument array contains the x and y + * coordinates of the view relative to the view's left and top edges, * respectively. * * @param outLocation A two-element integer array in which the view @@ -25637,8 +25694,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * restricted to a single screen, the coordinate space includes only the * screen on which the app is running. * - * <p>After the method returns, the argument array contains the x- and - * y-coordinates of the view relative to the view's left and top edges, + * <p>After the method returns, the argument array contains the x and y + * coordinates of the view relative to the view's left and top edges, * respectively. * * @param outLocation A two-element integer array in which the view diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fbb86ff3a55a..b7a2aa0b0174 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5035,9 +5035,6 @@ public final class ViewRootImpl implements ViewParent, } void requestPointerCapture(boolean enabled) { - if (mPointerCapture == enabled) { - return; - } final IBinder inputToken = getInputToken(); if (inputToken == null) { Log.e(mTag, "No input channel to request Pointer Capture."); 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/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 79dac19d0927..7dc039d44f95 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -650,6 +650,26 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Requests focus on the top running Activity in the given TaskFragment. This will only take + * effect if there is no focus, or if the current focus is in the same Task as the requested + * TaskFragment. + * @param fragmentToken client assigned unique token to create TaskFragment with specified in + * {@link TaskFragmentCreationParams#getFragmentToken()}. + * @hide + */ + @NonNull + public WindowContainerTransaction requestFocusOnTaskFragment(@NonNull IBinder fragmentToken) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT) + .setContainer(fragmentToken) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + + } + + /** * When this {@link WindowContainerTransaction} failed to finish on the server side, it will * trigger callback with this {@param errorCallbackToken}. * @param errorCallbackToken client provided token that will be passed back as parameter in @@ -1057,6 +1077,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 15; public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16; public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17; + public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1368,6 +1389,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER: return "{removeLocalInsetsProvider: container=" + mContainer + " insetsType=" + Arrays.toString(mInsetsTypes) + "}"; + case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT: + return "{requestFocusOnTaskFragment: container=" + mContainer + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop 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/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 52d54cd1f717..681693b1dbad 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -284,4 +284,9 @@ interface IVoiceInteractionManagerService { * Stops to listen the status of visible activity. */ void stopListeningVisibleActivityChanged(in IBinder token); + + /** + * Notifies when the session window is shown or hidden. + */ + void setSessionWindowVisible(in IBinder token, boolean visible); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl index bc757e24c852..6e409885fa13 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl @@ -30,6 +30,11 @@ void onVoiceSessionHidden(); /** + * Called when a voice session window is shown/hidden. + */ + void onVoiceSessionWindowVisibilityChanged(boolean visible); + + /** * Called when UI hints were received. */ void onSetUiHints(in Bundle args); 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/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 351ac4587def..0a07e0a04a40 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -233,8 +233,14 @@ public class ResolverListAdapter extends BaseAdapter { // copied the original unfiltered items to a separate List instance and can now filter // the remainder in-place without any further bookkeeping. boolean needsCopyOfUnfiltered = (mUnfilteredResolveList == currentResolveList); - mUnfilteredResolveList = performSecondaryResolveListFiltering( + List<ResolvedComponentInfo> originalList = performSecondaryResolveListFiltering( currentResolveList, needsCopyOfUnfiltered); + if (originalList != null) { + // Only need the originalList value if there was a modification (otherwise it's null + // and shouldn't overwrite mUnfilteredResolveList). + mUnfilteredResolveList = originalList; + } + return finishRebuildingListWithFilteredResults(currentResolveList, doPostProcessing); } @@ -293,7 +299,7 @@ public class ResolverListAdapter extends BaseAdapter { * appearing in the rebuilt-list results, while still considering those items for the "other * profile" special-treatment described in {@code rebuildList()}. * - * @return the same (possibly null) List reference as {@code currentResolveList}, if the list is + * @return the same (possibly null) List reference as {@code currentResolveList} if the list is * unmodified as a result of filtering; or, if some item(s) were removed, then either a copy of * the original {@code currentResolveList} (if {@code returnCopyOfOriginalListIfModified} is * true), or null (otherwise). 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/policy/ForceShowNavBarSettingsObserver.java b/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java new file mode 100644 index 000000000000..fc064ea1ff10 --- /dev/null +++ b/core/java/com/android/internal/policy/ForceShowNavBarSettingsObserver.java @@ -0,0 +1,75 @@ +/* + * 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.internal.policy; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +/** + * A ContentObserver for listening {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE} setting key. + * + * @hide + */ +public class ForceShowNavBarSettingsObserver extends ContentObserver { + private Context mContext; + private Runnable mOnChangeRunnable; + + public ForceShowNavBarSettingsObserver(Handler handler, Context context) { + super(handler); + mContext = context; + } + + public void setOnChangeRunnable(Runnable r) { + mOnChangeRunnable = r; + } + + /** + * Registers the observer. + */ + public void register() { + final ContentResolver r = mContext.getContentResolver(); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_FORCE_VISIBLE), + false, this, UserHandle.USER_ALL); + } + + /** + * Unregisters the observer. + */ + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + if (mOnChangeRunnable != null) { + mOnChangeRunnable.run(); + } + } + + /** + * Returns true only when it's in orce show navigation bar mode. Otherwise, return false. + */ + public boolean isEnabled() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.NAV_BAR_FORCE_VISIBLE, 0, UserHandle.USER_CURRENT) == 1; + } +} 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/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 51a708b76801..c769da57eecc 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -322,7 +322,8 @@ public: env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, gScreenshotHardwareBufferClassInfo.builder, jhardwareBuffer, namedColorSpace, - captureResults.capturedSecureLayers); + captureResults.capturedSecureLayers, + captureResults.capturedHdrLayers); env->CallVoidMethod(screenCaptureListenerObject, gScreenCaptureListenerClassInfo.onScreenCaptureComplete, screenshotHardwareBuffer); @@ -2399,7 +2400,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) MakeGlobalRefOrDie(env, screenshotGraphicsBufferClazz); gScreenshotHardwareBufferClassInfo.builder = GetStaticMethodIDOrDie(env, screenshotGraphicsBufferClazz, "createFromNative", - "(Landroid/hardware/HardwareBuffer;IZ)Landroid/view/" + "(Landroid/hardware/HardwareBuffer;IZZ)Landroid/view/" "SurfaceControl$ScreenshotHardwareBuffer;"); jclass displayedContentSampleClazz = FindClassOrDie(env, diff --git a/core/proto/android/os/appbackgroundrestrictioninfo.proto b/core/proto/android/os/appbackgroundrestrictioninfo.proto index 502fd64f97e4..5bf8ea79a8ea 100644 --- a/core/proto/android/os/appbackgroundrestrictioninfo.proto +++ b/core/proto/android/os/appbackgroundrestrictioninfo.proto @@ -73,12 +73,17 @@ message AppBackgroundRestrictionsInfo { optional FgsTrackerInfo fgs_tracker_info = 5; message BatteryTrackerInfo { - // total battery usage within last 24h (percentage) + // total battery usage within last 24h (1/10000th) optional int32 battery_24h = 1; - // background battery usage (percentage) + // background battery usage (1/10000th) optional int32 battery_usage_background = 2; - // FGS battery usage (percentage) + // FGS battery usage (1/10000th) optional int32 battery_usage_fgs = 3; + // Foreground battery usage (1/10000th) + optional int32 battery_usage_foreground = 4; + // Cached battery usage (1/10000th) + optional int32 battery_usage_cached = 5; + } optional BatteryTrackerInfo battery_tracker_info = 6; @@ -197,5 +202,8 @@ message AppBackgroundRestrictionsInfo { // indicates if the current device is a low ram device. optional bool low_mem_device = 12; + + // indicates previous background restriction level. + optional RestrictionLevel previous_restriction_level = 13; } 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/config_telephony.xml b/core/res/res/values/config_telephony.xml index 77500c42c6bf..d9ac5164f705 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -109,4 +109,12 @@ <!-- Telephony qualified networks service class name to bind to by default. --> <string name="config_qualified_networks_service_class" translatable="false"></string> <java-symbol type="string" name="config_qualified_networks_service_class" /> + + <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks + will not perform handover if the target transport is out of service, or VoPS not + supported. The network will be torn down on the source transport, and will be + re-established on the target transport when condition is allowed for bringing up a + new network. --> + <bool name="config_enhanced_iwlan_handover_check">true</bool> + <java-symbol type="bool" name="config_enhanced_iwlan_handover_check" /> </resources> 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/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index b04b82629b92..a76d74edc0f4 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -128,6 +128,7 @@ public final class Icon implements Parcelable { // TYPE_RESOURCE: Resources // TYPE_DATA: DataBytes private Object mObj1; + private boolean mCachedAshmem = false; // TYPE_RESOURCE: package name // TYPE_URI: uri string @@ -156,6 +157,8 @@ public final class Icon implements Parcelable { /** * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} or * {@link #TYPE_ADAPTIVE_BITMAP} Icon. + * + * Note that this will always return an immutable Bitmap. * @hide */ @UnsupportedAppUsage @@ -166,8 +169,20 @@ public final class Icon implements Parcelable { return (Bitmap) mObj1; } + /** + * Sets the Icon's contents to a particular Bitmap. Note that this may make a copy of the Bitmap + * if the supplied Bitmap is mutable. In that case, the value returned by getBitmap() may not + * equal the Bitmap passed to setBitmap(). + * + * @hide + */ private void setBitmap(Bitmap b) { - mObj1 = b; + if (b.isMutable()) { + mObj1 = b.copy(b.getConfig(), false); + } else { + mObj1 = b; + } + mCachedAshmem = false; } /** @@ -488,6 +503,7 @@ public final class Icon implements Parcelable { getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) { setBitmap(getBitmap().asShared()); } + mCachedAshmem = true; } /** @@ -913,7 +929,10 @@ public final class Icon implements Parcelable { switch (mType) { case TYPE_BITMAP: case TYPE_ADAPTIVE_BITMAP: - final Bitmap bits = getBitmap(); + if (!mCachedAshmem) { + mObj1 = ((Bitmap) mObj1).asShared(); + mCachedAshmem = true; + } getBitmap().writeToParcel(dest, flags); break; case TYPE_RESOURCE: diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 9f33cbcbcbd5..2328f76a7130 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -112,9 +112,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, - @Nullable Consumer<Exception> failureCallback) { + @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) { try { - mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule); + mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule, + isPlaceholder); } catch (Exception e) { if (failureCallback != null) { failureCallback.accept(e); @@ -710,8 +711,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // TODO(b/190433398): Handle failed request - startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), null, - placeholderRule, null); + startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), null /* options */, + placeholderRule, null /* failureCallback */, true /* isPlaceholder */); return true; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 716a087203d3..ee5a322eed4f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -217,12 +217,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param launchingActivity An activity that should be in the primary container. If it is not * currently in an existing container, a new one will be created and * the activity will be re-parented to it. - * @param activityIntent The intent to start the new activity. - * @param activityOptions The options to apply to new activity start. - * @param rule The split rule to be applied to the container. + * @param activityIntent The intent to start the new activity. + * @param activityOptions The options to apply to new activity start. + * @param rule The split rule to be applied to the container. + * @param isPlaceholder Whether the launch is a placeholder. */ void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent, - @Nullable Bundle activityOptions, @NonNull SplitRule rule) { + @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { final Rect parentBounds = getParentContainerBounds(launchingActivity); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, isLtr(launchingActivity, rule)); @@ -244,6 +245,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, activityIntent, activityOptions, rule); + if (isPlaceholder) { + // When placeholder is launched in split, we should keep the focus on the primary. + wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); + } applyTransaction(wct); primaryContainer.setLastRequestedBounds(primaryRectBounds); @@ -272,14 +277,21 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { isLtr); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr); + final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); + // Whether the placeholder is becoming side-by-side with the primary from fullscreen. + final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() + && secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */) + && !secondaryRectBounds.isEmpty(); // If the task fragments are not registered yet, the positions will be updated after they // are created again. resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); - final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + if (isPlaceholderBecomingSplit) { + // When placeholder is shown in split, we should keep the focus on the primary. + wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); + } } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, 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/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index a2b35fc9211a..a089585a5a00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -454,8 +454,11 @@ public class BubbleExpandedView extends LinearLayout { p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight())); p.endRecording(); Bitmap snapshot = Bitmap.createBitmap(p); - return new SurfaceControl.ScreenshotHardwareBuffer(snapshot.getHardwareBuffer(), - snapshot.getColorSpace(), false /* containsSecureLayers */); + return new SurfaceControl.ScreenshotHardwareBuffer( + snapshot.getHardwareBuffer(), + snapshot.getColorSpace(), + false /* containsSecureLayers */, + false /* containsHdrLayers */); } if (mTaskView == null || mTaskView.getSurfaceControl() == null) { return null; 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/core/java/com/android/internal/policy/KidsModeSettingsObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java index 8a1d407382bc..f8f9d6b8f8a0 100644 --- a/core/java/com/android/internal/policy/KidsModeSettingsObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeSettingsObserver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy; +package com.android.wm.shell.kidsmode; import android.content.ContentResolver; import android.content.Context; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 28681526b4f2..dc703583a449 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -40,7 +40,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.KidsModeSettingsObserver; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; 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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java index 5526d5be7594..440a6f8fb59a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -44,7 +44,6 @@ import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.internal.policy.KidsModeSettingsObserver; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; 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/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 440a54435fc3..affcf585904a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -332,6 +332,9 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { */ @Override public int compareTo(MediaDevice another) { + if (another == null) { + return -1; + } // Check Bluetooth device is have same connection state if (isConnected() ^ another.isConnected()) { if (isConnected()) { 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/SettingsLib/src/com/android/settingslib/wifi/WifiPermissionChecker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiPermissionChecker.java new file mode 100644 index 000000000000..2fe6e4695b3c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiPermissionChecker.java @@ -0,0 +1,98 @@ +/* + * 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.settingslib.wifi; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.ACCESS_WIFI_STATE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +/** + * Helper class to check Wi-Fi permissions. + */ +public class WifiPermissionChecker { + + private static final String TAG = "WifiPermChecker"; + + private IActivityManager mActivityManager; + private PackageManager mPackageManager; + private String mLaunchedPackage; + + public WifiPermissionChecker(Activity activity) { + this(activity, ActivityManager.getService()); + } + + public WifiPermissionChecker(Activity activity, IActivityManager activityManager) { + mActivityManager = activityManager; + mPackageManager = activity.getPackageManager(); + mLaunchedPackage = getLaunchedFromPackage(activity); + } + + /** + * Returns the launched package name + */ + public String getLaunchedPackage() { + return mLaunchedPackage; + } + + /** + * Returns whether the launched package can access Wi-Fi information + */ + public boolean canAccessWifiState() { + return checkPermission(ACCESS_WIFI_STATE); + } + + /** + * Returns whether the launched package can access precise location + */ + public boolean canAccessFineLocation() { + return checkPermission(ACCESS_FINE_LOCATION); + } + + private boolean checkPermission(String permission) { + if (mPackageManager == null || TextUtils.isEmpty(mLaunchedPackage)) { + Log.e(TAG, "Failed to check package permission!" + + " {PackageManager:" + mPackageManager + + ", LaunchedPackage:" + mLaunchedPackage + "}"); + return false; + } + + if (mPackageManager.checkPermission(permission, mLaunchedPackage) == PERMISSION_GRANTED) { + return true; + } + + Log.w(TAG, "The launched package does not have the required permission!" + + " {LaunchedPackage:" + mLaunchedPackage + ", Permission:" + permission + "}"); + return false; + } + + private String getLaunchedFromPackage(Activity activity) { + try { + return mActivityManager.getLaunchedFromPackage(activity.getActivityToken()); + } catch (RemoteException e) { + Log.e(TAG, "Can not get the launched package from activity manager!"); + return null; + } + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiPermissionCheckerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiPermissionCheckerTest.java new file mode 100644 index 000000000000..ec84141360e4 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiPermissionCheckerTest.java @@ -0,0 +1,111 @@ +/* + * 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.settingslib.wifi; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.ACCESS_WIFI_STATE; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.IActivityManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class WifiPermissionCheckerTest { + + static final String LAUNCHED_PACKAGE = "TestPackage"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + PackageManager mPackageManager; + @Mock + IActivityManager mActivityManager; + @Mock + Activity mActivity; + + WifiPermissionChecker mWifiPermissionChecker; + + @Before + public void setUp() { + when(mActivity.getPackageManager()).thenReturn(mPackageManager); + fakeGetLaunchedFromPackage(LAUNCHED_PACKAGE); + + mWifiPermissionChecker = new WifiPermissionChecker(mActivity, mActivityManager); + } + + @Test + public void getLaunchedPackage_returnLaunchedFromPackage() { + assertThat(mWifiPermissionChecker.getLaunchedPackage()).isEqualTo(LAUNCHED_PACKAGE); + } + + @Test + public void canAccessWifiState_noPermission_returnFalse() { + when(mPackageManager.checkPermission(ACCESS_WIFI_STATE, LAUNCHED_PACKAGE)) + .thenReturn(PERMISSION_DENIED); + + assertThat(mWifiPermissionChecker.canAccessWifiState()).isFalse(); + } + + @Test + public void canAccessWifiState_hasPermission_returnTrue() { + when(mPackageManager.checkPermission(ACCESS_WIFI_STATE, LAUNCHED_PACKAGE)) + .thenReturn(PERMISSION_GRANTED); + + assertThat(mWifiPermissionChecker.canAccessWifiState()).isTrue(); + } + + @Test + public void canAccessFineLocation_noPermission_returnFalse() { + when(mPackageManager.checkPermission(ACCESS_FINE_LOCATION, LAUNCHED_PACKAGE)) + .thenReturn(PERMISSION_DENIED); + + assertThat(mWifiPermissionChecker.canAccessFineLocation()).isFalse(); + } + + @Test + public void canAccessFineLocation_hasPermission_returnTrue() { + when(mPackageManager.checkPermission(ACCESS_FINE_LOCATION, LAUNCHED_PACKAGE)) + .thenReturn(PERMISSION_GRANTED); + + assertThat(mWifiPermissionChecker.canAccessFineLocation()).isTrue(); + } + + void fakeGetLaunchedFromPackage(String packageName) { + try { + when(mActivityManager.getLaunchedFromPackage(any())).thenReturn(packageName); + } catch (RemoteException e) { + // Do nothing + } + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index f949f99673d9..3029781f3e99 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -117,6 +117,9 @@ public class SecureSettings { Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, Settings.Secure.FACE_UNLOCK_APP_ENABLED, Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, + Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, + Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, Settings.Secure.VR_DISPLAY_MODE, Settings.Secure.NOTIFICATION_BADGING, Settings.Secure.NOTIFICATION_DISMISS_RTL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 2bdf81912709..a4da49713f87 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -173,6 +173,9 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR); 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-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml index 9ffafbc8cc09..6757acf7014a 100644 --- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml +++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml @@ -55,6 +55,15 @@ android:textColor="?android:attr/textColorSecondary"/> <ImageView + android:id="@+id/fgs_new" + android:layout_width="12dp" + android:layout_height="12dp" + android:scaleType="fitCenter" + android:src="@drawable/fgs_dot" + android:contentDescription="@string/fgs_dot_content_description" + /> + + <ImageView android:id="@+id/footer_icon" android:layout_width="@dimen/qs_footer_icon_size" android:layout_height="@dimen/qs_footer_icon_size" @@ -82,7 +91,7 @@ android:textColor="?android:attr/textColorPrimary" android:textSize="18sp"/> <ImageView - android:id="@+id/fgs_new" + android:id="@+id/fgs_collapsed_new" android:layout_width="12dp" android:layout_height="12dp" android:scaleType="fitCenter" 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/layout/media_output_broadcast_update_dialog.xml b/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml index 8b7a019b791b..89dcbcc5929f 100644 --- a/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_broadcast_update_dialog.xml @@ -19,11 +19,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="?android:attr/dialogPreferredPadding" - android:paddingRight="?android:attr/dialogPreferredPadding"> + android:paddingRight="?android:attr/dialogPreferredPadding" + android:orientation="vertical"> <EditText android:id="@+id/broadcast_edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="48dp" android:textAlignment="viewStart"/> + <TextView + android:id="@+id/broadcast_error_message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + style="@style/TextAppearance.ErrorText" + android:visibility="invisible"/> </LinearLayout>
\ 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 2264671e5067..008299bd9b1c 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -60,11 +60,15 @@ <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> <dimen name="qs_security_footer_background_inset">0dp</dimen> + <dimen name="qs_panel_padding_top">8dp</dimen> + <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> <dimen name="large_dialog_width">472dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 83b1b52fff3d..c8fff39cc8e5 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -711,4 +711,10 @@ <item>@*android:string/status_bar_alarm_clock</item> <item>@*android:string/status_bar_call_strength</item> </string-array> + + <!-- Packages of SystemUI --> + <string-array name="system_ui_packages" translatable="false"> + <item>com.android.keyguard</item> + <item>com.android.systemui</item> + </string-array> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4a8fd1b00dde..0a858af31964 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1179,6 +1179,10 @@ <!-- Maximum over scroll amount for the shade when transition to the full shade. --> <dimen name="lockscreen_shade_max_over_scroll_amount">24dp</dimen> + <!-- Maximum over scroll amount for the shade when transition to the full shade. + Only used for split-shade. --> + <dimen name="shade_max_over_scroll_amount">@dimen/lockscreen_shade_max_over_scroll_amount</dimen> + <!-- Maximum overshoot for the pulse expansion --> <dimen name="pulse_expansion_max_top_overshoot">32dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f92d6238e863..6cc61b4706f6 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> @@ -2290,6 +2293,11 @@ <string name="media_output_broadcast_starting">Starting…</string> <!-- The button text when Broadcast start failed [CHAR LIMIT=60] --> <string name="media_output_broadcast_start_failed">Can\u2019t broadcast</string> + <!-- The error message when Broadcast name/code update failed [CHAR LIMIT=60] --> + <string name="media_output_broadcast_update_error">Can\u2019t save. Try again.</string> + <!-- The error message when Broadcast name/code update failed and can't change again[CHAR LIMIT=60] --> + <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string> + <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_clip_data_label">Build number</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/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239730d18934..95cda8b9dfca 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -668,7 +668,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mVelocityTracker.recycle(); mVelocityTracker = null; } - mVibrator.cancel(); } private boolean inLockIconArea(MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index f925eaa0e40b..eb6705a2e979 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -15,6 +15,8 @@ */ package com.android.keyguard; +import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset; + import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -152,7 +154,7 @@ class NumPadAnimator { ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); TypedArray a = ctw.obtainStyledAttributes(customAttrs); - mNormalColor = Utils.getPrivateAttrColorIfUnset(ctw, a, 0, 0, + mNormalColor = 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/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 9d5b93c2b329..7c2673c31bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -187,6 +187,14 @@ public class AssistManager { } @Override + public void onVoiceSessionWindowVisibilityChanged(boolean visible) + throws RemoteException { + if (VERBOSE) { + Log.v(TAG, "Window visibility changed: " + visible); + } + } + + @Override public void onSetUiHints(Bundle hints) { if (VERBOSE) { Log.v(TAG, "UI hints received"); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index dbfce2ed2532..2f097924817e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -112,17 +112,16 @@ public class UdfpsDialogMeasureAdapter { if (child.getId() == R.id.biometric_icon_frame) { final FrameLayout iconFrame = (FrameLayout) child; final View icon = iconFrame.getChildAt(0); - - // Ensure that the icon is never larger than the sensor. - icon.measure( - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); - // Create a frame that's exactly the height of the sensor circle. iconFrame.measure( MeasureSpec.makeMeasureSpec( child.getLayoutParams().width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); + + // Ensure that the icon is never larger than the sensor. + icon.measure( + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); } else if (child.getId() == R.id.space_above_icon) { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), @@ -208,16 +207,15 @@ public class UdfpsDialogMeasureAdapter { if (child.getId() == R.id.biometric_icon_frame) { final FrameLayout iconFrame = (FrameLayout) child; final View icon = iconFrame.getChildAt(0); + // Create a frame that's exactly the height of the sensor circle. + iconFrame.measure( + MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); // Ensure that the icon is never larger than the sensor. icon.measure( MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); - - // Create a frame that's exactly the height of the sensor circle. - iconFrame.measure( - MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.space_above_icon) { // Adjust the width and height of the top spacer if necessary. final int newTopSpacerHeight = child.getLayoutParams().height diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index 842791f8ed93..937b81337cbb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -127,17 +127,19 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation); if (mAnimatingBetweenAodAndLockscreen && !mPauseAuth) { + mLockScreenFp.setTranslationX(mBurnInOffsetX); + mLockScreenFp.setTranslationY(mBurnInOffsetY); mBgProtection.setAlpha(1f - mInterpolatedDarkAmount); mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount); } else if (mInterpolatedDarkAmount == 0f) { + mLockScreenFp.setTranslationX(0); + mLockScreenFp.setTranslationY(0); mBgProtection.setAlpha(mAlpha / 255f); mLockScreenFp.setAlpha(mAlpha / 255f); } else { mBgProtection.setAlpha(0f); mLockScreenFp.setAlpha(0f); } - mLockScreenFp.setTranslationX(mBurnInOffsetX); - mLockScreenFp.setTranslationY(mBurnInOffsetY); mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount); mAodFp.setTranslationX(mBurnInOffsetX); 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/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 3ae11ff28345..9c7411bf3649 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -54,7 +54,7 @@ public class FragmentHostManager { private final View mRootView; private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_ASSETS_PATHS); + | ActivityInfo.CONFIG_ASSETS_PATHS); private final FragmentService mManager; private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager(); 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 2e1373259975..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; @@ -849,7 +850,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, @Override public void onLaunchAnimationCancelled() { - setOccluded(true /* occluded */, false /* animate */); + Log.d(TAG, "Occlude launch animation cancelled. " + + "Occluded state is now: " + mOccluded); } @NonNull @@ -894,7 +896,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, }; private IRemoteAnimationRunner mOccludeAnimationRunner = - new ActivityLaunchRemoteAnimationRunner(mOccludeAnimationController); + new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController); /** * Animation controller for activities that unocclude the keyguard. This does not use the @@ -919,13 +921,17 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - final RemoteAnimationTarget primary = apps[0]; + if (apps == null || apps.length == 0 || apps[0] == null) { + Log.d(TAG, "No apps provided to unocclude runner; " + + "skipping animation and unoccluding."); - if (primary == null) { finishedCallback.onAnimationFinished(); + setOccluded(false /* isOccluded */, true /* animate */); return; } + final RemoteAnimationTarget primary = apps[0]; + final SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier( mKeyguardViewControllerLazy.get().getViewRootImpl().getView()); @@ -965,6 +971,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, @Override public void onAnimationEnd(Animator animation) { try { + setOccluded(false /* isOccluded */, true /* animate */); finishedCallback.onAnimationFinished(); mUnoccludeAnimator = null; } catch (RemoteException e) { @@ -2301,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) { @@ -2318,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); @@ -2622,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; @@ -2654,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; } @@ -2790,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(); } @@ -3125,4 +3143,36 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); } } + + /** + * Subclass of {@link ActivityLaunchRemoteAnimationRunner} that calls {@link #setOccluded} when + * onAnimationStart is called. + */ + private class OccludeActivityLaunchRemoteAnimationRunner + extends ActivityLaunchRemoteAnimationRunner { + + OccludeActivityLaunchRemoteAnimationRunner( + ActivityLaunchAnimator.Controller controller) { + super(controller); + } + + @Override + public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + super.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); + + // This is the first signal we have from WM that we're going to be occluded. Set our + // internal state to reflect that immediately, vs. waiting for the launch animator to + // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to + // be occluded and might re-show the keyguard. + setOccluded(true /* isOccluded */, false /* animate */); + } + + @Override + public void onAnimationCancelled() throws RemoteException { + super.onAnimationCancelled(); + Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index 5a214d1cd5e0..a6b4f1d2a9d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -21,24 +21,41 @@ 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 /** - * ColorTransition is responsible for managing the animation between two specific colors. + * 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. * 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 ColorTransition( +open class AnimatingColorTransition( private val defaultColor: Int, private val extractColor: (ColorScheme) -> Int, private val applyColor: (Int) -> Unit -) : AnimatorUpdateListener { +) : AnimatorUpdateListener, ColorTransition { private val argbEvaluator = ArgbEvaluator() private val valueAnimator = buildAnimator() @@ -53,7 +70,7 @@ open class ColorTransition( applyColor(currentColor) } - fun updateColorScheme(scheme: ColorScheme?) { + override fun updateColorScheme(scheme: ColorScheme?) { val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme) if (newTargetColor != targetColor) { sourceColor = currentColor @@ -76,7 +93,9 @@ open class ColorTransition( } } -typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> ColorTransition +typealias AnimatingColorTransitionFactory = + (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition +typealias GenericColorTransitionFactory = ((ColorScheme?) -> Unit) -> GenericColorTransition /** * ColorSchemeTransition constructs a ColorTransition for each color in the scheme @@ -86,27 +105,26 @@ typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> class ColorSchemeTransition internal constructor( private val context: Context, mediaViewHolder: MediaViewHolder, - colorTransitionFactory: ColorTransitionFactory + animatingColorTransitionFactory: AnimatingColorTransitionFactory, + genericColorTransitionFactory: GenericColorTransitionFactory ) { constructor(context: Context, mediaViewHolder: MediaViewHolder) : - this(context, mediaViewHolder, ::ColorTransition) + this(context, mediaViewHolder, ::AnimatingColorTransition, ::GenericColorTransition) val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) - val surfaceColor = colorTransitionFactory( + val surfaceColor = animatingColorTransitionFactory( 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 = colorTransitionFactory( + val accentPrimary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::accentPrimaryFromScheme ) { accentPrimary -> @@ -116,7 +134,7 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) } - val textPrimary = colorTransitionFactory( + val textPrimary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::textPrimaryFromScheme ) { textPrimary -> @@ -132,28 +150,65 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) } - val textPrimaryInverse = colorTransitionFactory( + val textPrimaryInverse = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimaryInverse), ::textPrimaryInverseFromScheme ) { textPrimaryInverse -> mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse) } - val textSecondary = colorTransitionFactory( + val textSecondary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorSecondary), ::textSecondaryFromScheme ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) } - val textTertiary = colorTransitionFactory( + val textTertiary = animatingColorTransitionFactory( 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) + surfaceColor, + accentPrimary, + textPrimary, + textPrimaryInverse, + textSecondary, + textTertiary, + backgroundGradient + ) 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 97c6014c91bd..5e767b0458b9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt @@ -35,3 +35,9 @@ 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/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d2c35bd96d5a..d9ee8f3f06b4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -30,10 +30,12 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Rect; import android.graphics.drawable.Animatable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.graphics.drawable.TransitionDrawable; @@ -159,6 +161,7 @@ public class MediaControlPanel { private MetadataAnimationHandler mMetadataAnimationHandler; private ColorSchemeTransition mColorSchemeTransition; private Drawable mPrevArtwork = null; + private boolean mIsArtworkBound = false; private int mArtworkBoundId = 0; private int mArtworkNextBindRequestId = 0; @@ -586,6 +589,9 @@ public class MediaControlPanel { private void bindArtworkAndColors(MediaData data, boolean updateBackground) { final int reqId = mArtworkNextBindRequestId++; + if (updateBackground) { + mIsArtworkBound = false; + } // Capture width & height from views in foreground for artwork scaling in background int width = mMediaViewHolder.getPlayer().getWidth(); @@ -597,15 +603,18 @@ public class MediaControlPanel { // Album art ColorScheme mutableColorScheme = null; Drawable artwork; + boolean isArtworkBound; Icon artworkIcon = data.getArtwork(); if (artworkIcon != null) { WallpaperColors wallpaperColors = WallpaperColors .fromBitmap(artworkIcon.getBitmap()); mutableColorScheme = new ColorScheme(wallpaperColors, true); artwork = getScaledBackground(artworkIcon, width, height); + isArtworkBound = true; } else { // If there's no artwork, use colors from the app icon - artwork = null; + artwork = new ColorDrawable(Color.TRANSPARENT); + isArtworkBound = false; try { Drawable icon = mContext.getPackageManager() .getApplicationIcon(data.getPackageName()); @@ -625,16 +634,20 @@ public class MediaControlPanel { ImageView albumView = mMediaViewHolder.getAlbumView(); albumView.setPadding(0, 0, 0, 0); albumView.setClipToOutline(true); - if (updateBackground) { - if (mPrevArtwork == null || artwork == null) { + if (updateBackground || (!mIsArtworkBound && isArtworkBound)) { + if (mPrevArtwork == null) { albumView.setImageDrawable(artwork); } else { + // Since we throw away the last transition, this'll pop if you backgrounds + // are cycled too fast (or the correct background arrives very soon after + // the metadata changes). TransitionDrawable transitionDrawable = new TransitionDrawable( - new Drawable[] { mPrevArtwork, artwork }); + new Drawable[]{mPrevArtwork, artwork}); albumView.setImageDrawable(transitionDrawable); - transitionDrawable.startTransition(333); + transitionDrawable.startTransition(isArtworkBound ? 333 : 80); } mPrevArtwork = artwork; + mIsArtworkBound = isArtworkBound; } // Transition Colors to current color scheme diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index b2751cec5d9e..426b45a31550 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -261,6 +261,8 @@ class MediaDataManager( // Set up links back into the pipeline for listeners that need to send events upstream. mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean -> setTimedOut(key, timedOut) } + mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState -> + updateState(key, state) } mediaResumeListener.setManager(this) mediaDataFilter.mediaDataManager = this @@ -502,6 +504,21 @@ class MediaDataManager( } } + /** + * Called when the player's [PlaybackState] has been updated with new actions and/or state + */ + private fun updateState(key: String, state: PlaybackState) { + mediaEntries.get(key)?.let { + val actions = createActionsFromState(it.packageName, + mediaControllerFactory.create(it.token), UserHandle(it.userId)) + val data = it.copy( + semanticActions = actions, + isPlaying = isPlayingState(state.state)) + if (DEBUG) Log.d(TAG, "State updated outside of notification") + onMediaDataLoaded(key, key, data) + } + } + private fun removeEntry(key: String) { mediaEntries.remove(key)?.let { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) @@ -673,11 +690,8 @@ class MediaDataManager( // Otherwise, use the notification actions var actionIcons: List<MediaAction> = emptyList() var actionsToShowCollapsed: List<Int> = emptyList() - var semanticActions: MediaButton? = null - if (mediaFlags.areMediaSessionActionsEnabled(sbn.packageName, sbn.user) && - mediaController.playbackState != null) { - semanticActions = createActionsFromState(sbn.packageName, mediaController) - } else { + val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user) + if (semanticActions == null) { val actions = createActionsFromNotification(sbn) actionIcons = actions.first actionsToShowCollapsed = actions.second @@ -789,13 +803,17 @@ class MediaDataManager( * @return a Pair consisting of a list of media actions, and a list of ints representing which * of those actions should be shown in the compact player */ - private fun createActionsFromState(packageName: String, controller: MediaController): - MediaButton? { + private fun createActionsFromState( + packageName: String, + controller: MediaController, + user: UserHandle + ): MediaButton? { val state = controller.playbackState - if (state == null) { - return MediaButton() + if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) { + return null } - // First, check for} standard actions + + // First, check for standard actions val playOrPause = if (isConnectingState(state.state)) { // Spinner needs to be animating to render anything. Start it here. val drawable = context.getDrawable( diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 8c6710a6fd68..fc8d38d59d59 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -55,6 +55,13 @@ class MediaTimeoutListener @Inject constructor( */ lateinit var timeoutCallback: (String, Boolean) -> Unit + /** + * Callback representing that a media object [PlaybackState] has changed. + * @param key Media control unique identifier + * @param state The new [PlaybackState] + */ + lateinit var stateCallback: (String, PlaybackState) -> Unit + override fun onMediaDataLoaded( key: String, oldKey: String?, @@ -85,17 +92,17 @@ class MediaTimeoutListener @Inject constructor( } reusedListener?.let { - val wasPlaying = it.playing ?: false + val wasPlaying = it.isPlaying() logger.logUpdateListener(key, wasPlaying) it.mediaData = data it.key = key mediaListeners[key] = it - if (wasPlaying != it.playing) { + if (wasPlaying != it.isPlaying()) { // If a player becomes active because of a migration, we'll need to broadcast // its state. Doing it now would lead to reentrant callbacks, so let's wait // until we're done. mainExecutor.execute { - if (mediaListeners[key]?.playing == true) { + if (mediaListeners[key]?.isPlaying() == true) { logger.logDelayedUpdate(key) timeoutCallback.invoke(key, false /* timedOut */) } @@ -121,7 +128,7 @@ class MediaTimeoutListener @Inject constructor( ) : MediaController.Callback() { var timedOut = false - var playing: Boolean? = null + var lastState: PlaybackState? = null var resumption: Boolean? = null var destroyed = false @@ -145,6 +152,9 @@ class MediaTimeoutListener @Inject constructor( private var mediaController: MediaController? = null private var cancellation: Runnable? = null + fun Int.isPlaying() = isPlayingState(this) + fun isPlaying() = lastState?.state?.isPlaying() ?: false + init { mediaData = data } @@ -175,16 +185,26 @@ class MediaTimeoutListener @Inject constructor( private fun processState(state: PlaybackState?, dispatchEvents: Boolean) { logger.logPlaybackState(key, state) - val isPlaying = state != null && isPlayingState(state.state) + val playingStateSame = (state?.state?.isPlaying() == isPlaying()) + val actionsSame = (lastState?.actions == state?.actions) && + areCustomActionListsEqual(lastState?.customActions, state?.customActions) val resumptionChanged = resumption != mediaData.resumption - if (playing == isPlaying && playing != null && !resumptionChanged) { + + lastState = state + + if ((!actionsSame || !playingStateSame) && state != null && dispatchEvents) { + logger.logStateCallback(key) + stateCallback.invoke(key, state) + } + + if (playingStateSame && !resumptionChanged) { return } - playing = isPlaying resumption = mediaData.resumption - if (!isPlaying) { - logger.logScheduleTimeout(key, isPlaying, resumption!!) + val playing = isPlaying() + if (!playing) { + logger.logScheduleTimeout(key, playing, resumption!!) if (cancellation != null && !resumptionChanged) { // if the media changed resume state, we'll need to adjust the timeout length logger.logCancelIgnored(key) @@ -220,4 +240,50 @@ class MediaTimeoutListener @Inject constructor( cancellation = null } } + + private fun areCustomActionListsEqual( + first: List<PlaybackState.CustomAction>?, + second: List<PlaybackState.CustomAction>? + ): Boolean { + // Same object, or both null + if (first === second) { + return true + } + + // Only one null, or different number of actions + if ((first == null || second == null) || (first.size != second.size)) { + return false + } + + // Compare individual actions + first.asSequence().zip(second.asSequence()).forEach { (firstAction, secondAction) -> + if (!areCustomActionsEqual(firstAction, secondAction)) { + return false + } + } + return true + } + + private fun areCustomActionsEqual( + firstAction: PlaybackState.CustomAction, + secondAction: PlaybackState.CustomAction + ): Boolean { + if (firstAction.action != secondAction.action || + firstAction.name != secondAction.name || + firstAction.icon != secondAction.icon) { + return false + } + + if ((firstAction.extras == null) != (secondAction.extras == null)) { + return false + } + if (firstAction.extras != null) { + firstAction.extras.keySet().forEach { key -> + if (firstAction.extras.get(key) != secondAction.extras.get(key)) { + return false + } + } + } + return true + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt index a86515990fcb..d9c58c0d0d76 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt @@ -102,6 +102,17 @@ class MediaTimeoutLogger @Inject constructor( } ) + fun logStateCallback(key: String) = buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = key + }, + { + "dispatching state update for $key" + } + ) + fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log( TAG, LogLevel.DEBUG, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 9b3b3ce6109f..dd4f1d6c9015 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -59,11 +59,14 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { private ImageView mBroadcastCodeEye; private Boolean mIsPasswordHide = true; private ImageView mBroadcastCodeEdit; - private Button mStopButton; + private AlertDialog mAlertDialog; + private TextView mBroadcastErrorMessage; static final int METADATA_BROADCAST_NAME = 0; static final int METADATA_BROADCAST_CODE = 1; + private static final int MAX_BROADCAST_INFO_UPDATE = 3; + MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { super(context, broadcastSender, mediaOutputController); @@ -118,14 +121,18 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { return View.VISIBLE; } - // TODO(b/222674827): To get the information from BluetoothLeBroadcastMetadata(Broadcast code) - // and BluetoothLeAudioContentMetadata(Program info) when start Broadcast is successful. - private String getBroadcastMetaDataInfo(int metaData) { - switch (metaData) { + @Override + public void onStopButtonClick() { + mMediaOutputController.stopBluetoothLeBroadcast(); + dismiss(); + } + + private String getBroadcastMetadataInfo(int metadata) { + switch (metadata) { case METADATA_BROADCAST_NAME: - return ""; + return mMediaOutputController.getBroadcastName(); case METADATA_BROADCAST_CODE: - return ""; + return mMediaOutputController.getBroadcastCode(); default: return ""; } @@ -164,13 +171,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { launchBroadcastUpdatedDialog(true, mBroadcastCode.getText().toString()); }); - mBroadcastName.setText(getBroadcastMetaDataInfo(METADATA_BROADCAST_NAME)); - mBroadcastCode.setText(getBroadcastMetaDataInfo(METADATA_BROADCAST_CODE)); - - mStopButton = getDialogView().requireViewById(R.id.stop); - mStopButton.setOnClickListener(v -> { - stopBroadcast(); - }); + mBroadcastName.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_NAME)); + mBroadcastCode.setText(getBroadcastMetadataInfo(METADATA_BROADCAST_CODE)); } private void inflateBroadcastInfoArea() { @@ -179,16 +181,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } private void setQrCodeView() { - //get the MetaData, and convert to BT QR code format. - String broadcastMetaData = getBroadcastMetaData(); - if (broadcastMetaData.isEmpty()) { + //get the Metadata, and convert to BT QR code format. + String broadcastMetadata = getBroadcastMetadata(); + if (broadcastMetadata.isEmpty()) { //TDOD(b/226708424) Error handling for unable to generate the QR code bitmap return; } try { final int qrcodeSize = getContext().getResources().getDimensionPixelSize( R.dimen.media_output_qrcode_size); - final Bitmap bmp = QrCodeGenerator.encodeQrCode(broadcastMetaData, qrcodeSize); + final Bitmap bmp = QrCodeGenerator.encodeQrCode(broadcastMetadata, qrcodeSize); mBroadcastQrCodeView.setImageBitmap(bmp); } catch (WriterException e) { //TDOD(b/226708424) Error handling for unable to generate the QR code bitmap @@ -203,50 +205,87 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { mIsPasswordHide = !mIsPasswordHide; } - private void launchBroadcastUpdatedDialog(boolean isPassword, String editString) { + private void launchBroadcastUpdatedDialog(boolean isBroadcastCode, String editString) { final View layout = LayoutInflater.from(mContext).inflate( R.layout.media_output_broadcast_update_dialog, null); final EditText editText = layout.requireViewById(R.id.broadcast_edit_text); editText.setText(editString); - final AlertDialog alertDialog = new Builder(mContext) - .setTitle(isPassword ? R.string.media_output_broadcast_code + mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message); + mAlertDialog = new Builder(mContext) + .setTitle(isBroadcastCode ? R.string.media_output_broadcast_code : R.string.media_output_broadcast_name) .setView(layout) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.media_output_broadcast_dialog_save, (d, w) -> { - updateBroadcast(isPassword, editText.getText().toString()); + updateBroadcastInfo(isBroadcastCode, editText.getText().toString()); }) .create(); - alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(alertDialog, true); - SystemUIDialog.registerDismissListener(alertDialog); - alertDialog.show(); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mAlertDialog, true); + SystemUIDialog.registerDismissListener(mAlertDialog); + mAlertDialog.show(); } - /** - * TODO(b/222674827): The method should be get the BluetoothLeBroadcastMetadata after - * starting the Broadcast session successfully. Then we will follow the BT QR code format - * that convert BluetoothLeBroadcastMetadata object to String format. - */ - private String getBroadcastMetaData() { - return "TEST"; + private String getBroadcastMetadata() { + return mMediaOutputController.getBroadcastMetadata(); } - /** - * TODO(b/222676140): These method are about the LE Audio Broadcast API. The framework APIS - * will be wrapped in SettingsLib. And the UI will be executed through it. - */ - private void updateBroadcast(boolean isPassword, String updatedString) { + private void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) { + Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + if (positiveBtn != null) { + positiveBtn.setEnabled(false); + } + if (isBroadcastCode) { + handleBroadcastCodeUpdated(updatedString); + } else { + handleBroadcastNameUpdated(updatedString); + } } - /** - * TODO(b/222676140): These method are about the LE Audio Broadcast API. The framework APIS - * will be wrapped in SettingsLib. And the UI will be executed through it. - */ - private void stopBroadcast() { - dismiss(); + private void handleBroadcastNameUpdated(String name) { + // TODO(b/230473995) Add the retry mechanism and error handling when update fails + String currentName = mMediaOutputController.getBroadcastName(); + int retryCount = MAX_BROADCAST_INFO_UPDATE; + mMediaOutputController.setBroadcastName(name); + if (!mMediaOutputController.updateBluetoothLeBroadcast()) { + mMediaOutputController.setBroadcastName(currentName); + handleLeUpdateBroadcastFailed(retryCount); + } + } + + private void handleBroadcastCodeUpdated(String newPassword) { + // TODO(b/230473995) Add the retry mechanism and error handling when update fails + String currentPassword = mMediaOutputController.getBroadcastCode(); + int retryCount = MAX_BROADCAST_INFO_UPDATE; + if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + mMediaOutputController.setBroadcastCode(currentPassword); + handleLeUpdateBroadcastFailed(retryCount); + return; + } + + mMediaOutputController.setBroadcastCode(newPassword); + if (!mMediaOutputController.startBluetoothLeBroadcast()) { + mMediaOutputController.setBroadcastCode(currentPassword); + handleLeUpdateBroadcastFailed(retryCount); + return; + } + + mAlertDialog.dismiss(); + } + + private void handleLeUpdateBroadcastFailed(int retryCount) { + final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + mBroadcastErrorMessage.setVisibility(View.VISIBLE); + if (retryCount < MAX_BROADCAST_INFO_UPDATE) { + if (positiveBtn != null) { + positiveBtn.setEnabled(true); + } + mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error); + } else { + mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error); + } } } 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/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 0fbec3baffa6..8723f4fd63bb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -63,7 +63,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; @@ -79,6 +78,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.phone.SystemUIDialog; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -678,6 +678,56 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); } + String getBroadcastName() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "getBroadcastName: LE Audio Broadcast is null"); + return ""; + } + return broadcast.getProgramInfo(); + } + + void setBroadcastName(String broadcastName) { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "setBroadcastName: LE Audio Broadcast is null"); + return; + } + broadcast.setProgramInfo(broadcastName); + } + + String getBroadcastCode() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "getBroadcastCode: LE Audio Broadcast is null"); + return ""; + } + return new String(broadcast.getBroadcastCode(), StandardCharsets.UTF_8); + } + + void setBroadcastCode(String broadcastCode) { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "setBroadcastCode: LE Audio Broadcast is null"); + return; + } + broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8)); + } + + String getBroadcastMetadata() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null"); + return ""; + } + return broadcast.getLocalBluetoothLeBroadcastMetaData().convertToQrCodeString(); + } + boolean isActiveRemoteDevice(@NonNull MediaDevice device) { final List<String> features = device.getFeatures(); return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK) @@ -723,6 +773,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return true; } + boolean updateBluetoothLeBroadcast() { + LocalBluetoothLeBroadcast broadcast = + mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); + if (broadcast == null) { + Log.d(TAG, "The broadcast profile is null"); + return false; + } + broadcast.updateBroadcast(getAppSourceName(), /*language*/ null); + return true; + } + void registerLeBroadcastServiceCallBack( @NonNull @CallbackExecutor Executor executor, @NonNull BluetoothLeBroadcast.Callback callback) { 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/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 34f771ce0431..ce50ddff7b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -39,6 +39,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private static final boolean DEBUG = false; private static final String CURRENT_PAGE = "current_page"; + private static final int NO_PAGE = -1; private static final String TAG = "PagedTileLayout"; private static final int REVEAL_SCROLL_DURATION_MILLIS = 750; @@ -109,13 +110,14 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } public void saveInstanceState(Bundle outState) { - outState.putInt(CURRENT_PAGE, getCurrentItem()); + int resolvedPage = mPageToRestore != NO_PAGE ? mPageToRestore : getCurrentPageNumber(); + outState.putInt(CURRENT_PAGE, resolvedPage); } public void restoreInstanceState(Bundle savedInstanceState) { // There's only 1 page at this point. We want to restore the correct page once the // pages have been inflated - mPageToRestore = savedInstanceState.getInt(CURRENT_PAGE, -1); + mPageToRestore = savedInstanceState.getInt(CURRENT_PAGE, NO_PAGE); } @Override @@ -151,12 +153,15 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public void onRtlPropertiesChanged(int layoutDirection) { + // The configuration change will change the flag in the view (that's returned in + // isLayoutRtl). As we detect the change, we use the cached direction to store the page + // before setting it. + final int page = getPageNumberForDirection(mLayoutDirection == LAYOUT_DIRECTION_RTL); super.onRtlPropertiesChanged(layoutDirection); if (mLayoutDirection != layoutDirection) { mLayoutDirection = layoutDirection; setAdapter(mAdapter); - setCurrentItem(0, false); - mPageToRestore = 0; + setCurrentItem(page, false); } } @@ -172,8 +177,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { * Obtains the current page number respecting RTL */ private int getCurrentPageNumber() { + return getPageNumberForDirection(isLayoutRtl()); + } + + private int getPageNumberForDirection(boolean isLayoutRTL) { int page = getCurrentItem(); - if (mLayoutDirection == LAYOUT_DIRECTION_RTL) { + if (isLayoutRTL) { page = mPages.size() - 1 - page; } return page; @@ -388,9 +397,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator.setNumPages(mPages.size()); setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); - if (mPageToRestore != -1) { + if (mPageToRestore != NO_PAGE) { setCurrentItem(mPageToRestore, false); - mPageToRestore = -1; + mPageToRestore = NO_PAGE; } } @@ -479,9 +488,27 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { maxHeight = height; } } + if (mPages.get(0).getParent() == null) { + // Measure page 0 so we know how tall it is if it's not attached to the pager. + mPages.get(0).measure(widthMeasureSpec, heightMeasureSpec); + int height = mPages.get(0).getMeasuredHeight(); + if (height > maxHeight) { + maxHeight = height; + } + } setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom()); } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (mPages.get(0).getParent() == null) { + // Layout page 0, so we can get the bottom of the tiles. We only do this if the page + // is not attached. + mPages.get(0).layout(l, t, r, b); + } + } + public int getColumnCount() { if (mPages.size() == 0) return 0; return mPages.get(0).mColumns; @@ -625,8 +652,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { if (mPageIndicator == null) return; if (mPageListener != null) { int pageNumber = isLayoutRtl() ? mPages.size() - 1 - position : position; - mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1 - : position == 0, pageNumber); + mPageListener.onPageChanged(pageNumber == 0, pageNumber); } } @@ -645,8 +671,8 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator.setLocation(mPageIndicatorPosition); if (mPageListener != null) { int pageNumber = isLayoutRtl() ? mPages.size() - 1 - position : position; - mPageListener.onPageChanged(positionOffsetPixels == 0 && - (isLayoutRtl() ? position == mPages.size() - 1 : position == 0), + mPageListener.onPageChanged( + positionOffsetPixels == 0 && pageNumber == 0, // Send only valid page number on integer pages positionOffsetPixels == 0 ? pageNumber : PageListener.INVALID_PAGE ); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java index 0fe909552cb1..abebf3e80b21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java @@ -61,6 +61,7 @@ public class QSFgsManagerFooter implements View.OnClickListener, private final View mNumberContainer; private final TextView mNumberView; private final ImageView mDotView; + private final ImageView mCollapsedDotView; @Nullable private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener; @@ -75,6 +76,7 @@ public class QSFgsManagerFooter implements View.OnClickListener, mNumberContainer = mRootView.findViewById(R.id.fgs_number_container); mNumberView = mRootView.findViewById(R.id.fgs_number); mDotView = mRootView.findViewById(R.id.fgs_new); + mCollapsedDotView = mRootView.findViewById(R.id.fgs_collapsed_new); mContext = rootView.getContext(); mMainExecutor = mainExecutor; mExecutor = executor; @@ -147,8 +149,10 @@ public class QSFgsManagerFooter implements View.OnClickListener, if (mFgsManagerController.shouldUpdateFooterVisibility()) { mRootView.setVisibility(mNumPackages > 0 && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE); - mDotView.setVisibility( - mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE); + int dotVis = + mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE; + mDotView.setVisibility(dotVis); + mCollapsedDotView.setVisibility(dotVis); if (mVisibilityChangedListener != null) { mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility()); } 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 270bdc785178..0a616c095551 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -17,6 +17,7 @@ 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 /** @@ -367,7 +368,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } if (startColorAlpha > 0f) { - canvas.drawColor(updateColorAlpha(revealGradientEndColor, startColorAlpha)) + canvas.drawColor(getColorWithAlpha(revealGradientEndColor, startColorAlpha)) } with(shaderGradientMatrix) { @@ -383,15 +384,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, private fun setPaintColorFilter() { gradientPaint.colorFilter = PorterDuffColorFilter( - updateColorAlpha(revealGradientEndColor, revealGradientEndColorAlpha), + getColorWithAlpha(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/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index a4e2d5ec0829..a35bced819c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -28,7 +28,8 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.os.UserHandle -import android.provider.Settings +import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS +import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS import android.util.Log import android.view.View import android.view.ViewGroup @@ -85,6 +86,7 @@ class LockscreenSmartspaceController @Inject constructor( // Smartspace can be used on multiple displays, such as when the user casts their screen private var smartspaceViews = mutableSetOf<SmartspaceView>() + private var showNotifications = false private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null @@ -233,7 +235,13 @@ class LockscreenSmartspaceController @Inject constructor( deviceProvisionedController.removeCallback(deviceProvisionedListener) userTracker.addCallback(userTrackerCallback, uiExecutor) contentResolver.registerContentObserver( - secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + secureSettings.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + true, + settingsObserver, + UserHandle.USER_ALL + ) + contentResolver.registerContentObserver( + secureSettings.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS), true, settingsObserver, UserHandle.USER_ALL @@ -286,6 +294,9 @@ class LockscreenSmartspaceController @Inject constructor( } private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { + if (!showNotifications) { + return t.getFeatureType() == SmartspaceTarget.FEATURE_WEATHER + } return when (t.userHandle) { userTracker.userHandle -> { !t.isSensitive || showSensitiveContentForCurrentUser @@ -310,16 +321,26 @@ class LockscreenSmartspaceController @Inject constructor( } private fun reloadSmartspace() { - val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS - - showSensitiveContentForCurrentUser = - secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1 + showNotifications = secureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 0, + userTracker.userId + ) == 1 + + showSensitiveContentForCurrentUser = secureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + userTracker.userId + ) == 1 managedUserHandle = getWorkProfileUser() val managedId = managedUserHandle?.identifier if (managedId != null) { - showSensitiveContentForManagedUser = - secureSettings.getIntForUser(setting, 0, managedId) == 1 + showSensitiveContentForManagedUser = secureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + managedId + ) == 1 } session?.requestSmartspaceUpdate() 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/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index bf27550b331b..36cd173d2d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -415,6 +415,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mForwardScrollable; private boolean mBackwardScrollable; private NotificationShelf mShelf; + /** + * Limits the number of visible notifications. The remaining are collapsed in the notification + * shelf. -1 when there is no limit. + */ private int mMaxDisplayedNotifications = -1; private float mKeyguardBottomPadding = -1; @VisibleForTesting int mStatusBarHeight; @@ -1323,7 +1327,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private float updateStackEndHeight(float height, float bottomMargin, float topPadding) { - final float stackEndHeight = Math.max(0f, height - bottomMargin - topPadding); + final float stackEndHeight; + if (mMaxDisplayedNotifications != -1) { + // The stack intrinsic height already contains the correct value when there is a limit + // in the max number of notifications (e.g. as in keyguard). + stackEndHeight = mIntrinsicContentHeight; + } else { + stackEndHeight = Math.max(0f, height - bottomMargin - topPadding); + } mAmbientState.setStackEndHeight(stackEndHeight); return stackEndHeight; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index ae1fd2b180e5..2493ccbe5a48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -117,6 +117,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -178,6 +179,7 @@ public class NotificationStackScrollLayoutController { private final CentralSurfaces mCentralSurfaces; private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final ShadeTransitionController mShadeTransitionController; private final InteractionJankMonitor mJankMonitor; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; private final StackStateLogger mStackStateLogger; @@ -647,6 +649,7 @@ public class NotificationStackScrollLayoutController { NotifCollection notifCollection, NotificationEntryManager notificationEntryManager, LockscreenShadeTransitionController lockscreenShadeTransitionController, + ShadeTransitionController shadeTransitionController, IStatusBarService iStatusBarService, UiEventLogger uiEventLogger, LayoutInflater layoutInflater, @@ -675,6 +678,7 @@ public class NotificationStackScrollLayoutController { mLockscreenUserManager = lockscreenUserManager; mMetricsLogger = metricsLogger; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + mShadeTransitionController = shadeTransitionController; mFalsingCollector = falsingCollector; mFalsingManager = falsingManager; mResources = resources; @@ -769,6 +773,7 @@ public class NotificationStackScrollLayoutController { mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming); mLockscreenShadeTransitionController.setStackScroller(this); + mShadeTransitionController.setNotificationStackScrollLayoutController(this); mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); @@ -1207,7 +1212,7 @@ public class NotificationStackScrollLayoutController { */ public void updateShowEmptyShadeView() { Trace.beginSection("NSSLC.updateShowEmptyShadeView"); - mShowEmptyShadeView = mBarState != KEYGUARD + mShowEmptyShadeView = mStatusBarStateController.getCurrentOrUpcomingState() != KEYGUARD && !mView.isQsFullScreen() && getVisibleNotificationCount() == 0; 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 b14e92bf39cf..62b11c59923f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -1748,6 +1748,23 @@ public class CentralSurfaces extends CoreStartable implements } @Override + public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { + super.onLaunchAnimationStart(isExpandingFullyAbove); + + // Double check that the keyguard is still showing and not going away, but if so + // set the keyguard occluded. Typically, WM will let KeyguardViewMediator know + // directly, but we're overriding that to play the custom launch animation, so + // we need to take care of that here. The unocclude animation is not overridden, + // so WM will call KeyguardViewMediator's unocclude animation runner when the + // activity is exited. + if (mKeyguardStateController.isShowing() + && !mKeyguardStateController.isKeyguardGoingAway()) { + mKeyguardViewMediator.setOccluded(true /* isOccluded */, + true /* animate */); + } + } + + @Override public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the // animation so that we can assume that mIsLaunchingActivityOverLockscreen @@ -2934,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(); @@ -4514,9 +4529,12 @@ public class CentralSurfaces extends CoreStartable implements * @return UserHandle */ private UserHandle getActivityUserHandle(Intent intent) { - if (intent.getComponent() != null - && mContext.getPackageName().equals(intent.getComponent().getPackageName())) { - return new UserHandle(UserHandle.myUserId()); + String[] packages = mContext.getResources().getStringArray(R.array.system_ui_packages); + for (String pkg : packages) { + if (intent.getComponent() == null) break; + if (pkg.equals(intent.getComponent().getPackageName())) { + return new UserHandle(UserHandle.myUserId()); + } } return UserHandle.CURRENT; } 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 adf70a255b4f..0e8c8a2b4f2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -179,6 +179,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.phone.panelstate.PanelState; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -310,6 +311,7 @@ public class NotificationPanelViewController extends PanelViewController { private final NotificationRemoteInputManager mRemoteInputManager; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final ShadeTransitionController mShadeTransitionController; private final TapAgainViewController mTapAgainViewController; private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; private final RecordingController mRecordingController; @@ -745,7 +747,8 @@ public class NotificationPanelViewController extends PanelViewController { NotificationListContainer notificationListContainer, PanelEventsEmitter panelEventsEmitter, NotificationStackSizeCalculator notificationStackSizeCalculator, - UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + ShadeTransitionController shadeTransitionController) { super(view, falsingManager, dozeLog, @@ -826,7 +829,9 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBypassController = bypassController; mUpdateMonitor = keyguardUpdateMonitor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + mShadeTransitionController = shadeTransitionController; lockscreenShadeTransitionController.setNotificationPanelController(this); + shadeTransitionController.setNotificationPanelViewController(this); DynamicPrivacyControlListener dynamicPrivacyControlListener = new DynamicPrivacyControlListener(); @@ -885,7 +890,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 +910,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(); } } } @@ -1233,6 +1256,11 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea.initQRCodeScanner(mQRCodeScannerController); } + @VisibleForTesting + void setMaxDisplayedNotifications(int maxAllowed) { + mMaxAllowedKeyguardNotifications = maxAllowed; + } + private void updateMaxDisplayedNotifications(boolean recompute) { if (recompute) { mMaxAllowedKeyguardNotifications = Math.max(computeMaxKeyguardNotifications(), 1); @@ -1463,7 +1491,11 @@ public class NotificationPanelViewController extends PanelViewController { /** * @return the maximum keyguard notifications that can fit on the screen */ - private int computeMaxKeyguardNotifications() { + @VisibleForTesting + int computeMaxKeyguardNotifications() { + if (mAmbientState.getFractionToShade() > 0 || mAmbientState.getDozeAmount() > 0) { + return mMaxAllowedKeyguardNotifications; + } float topPadding = mNotificationStackScrollLayoutController.getTopPadding(); float shelfIntrinsicHeight = mNotificationShelfController.getVisibility() == View.GONE @@ -3169,12 +3201,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); @@ -3604,6 +3630,7 @@ public class NotificationPanelViewController extends PanelViewController { } }); mLockscreenShadeTransitionController.setQS(mQs); + mShadeTransitionController.setQs(mQs); mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader()); mQs.setScrollListener(mScrollListener); updateQsExpansion(); @@ -4921,6 +4948,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/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 9e707644782c..6637394e2b2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -108,6 +108,8 @@ public abstract class PanelViewController { */ private boolean mIsSpringBackAnimation; + private boolean mInSplitShade; + private void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); } @@ -303,8 +305,9 @@ public abstract class PanelViewController { mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); - mUnlockFalsingThreshold = mResources.getDimensionPixelSize( - R.dimen.unlock_falsing_threshold); + mUnlockFalsingThreshold = + mResources.getDimensionPixelSize(R.dimen.unlock_falsing_threshold); + mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade); } protected float getTouchSlop(MotionEvent event) { @@ -600,10 +603,12 @@ public abstract class PanelViewController { } mIsFlinging = true; // we want to perform an overshoot animation when flinging open - final boolean addOverscroll = expand - && mStatusBarStateController.getState() != StatusBarState.KEYGUARD - && mOverExpansion == 0.0f - && vel >= 0; + final boolean addOverscroll = + expand + && !mInSplitShade // Split shade has its own overscroll logic + && mStatusBarStateController.getState() != StatusBarState.KEYGUARD + && mOverExpansion == 0.0f + && vel >= 0; final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); float overshootAmount = 0.0f; if (addOverscroll) { @@ -777,7 +782,8 @@ public abstract class PanelViewController { } float maxPanelHeight = getMaxPanelHeight(); if (mHeightAnimator == null) { - if (mTracking) { + // Split shade has its own overscroll logic + if (mTracking && !mInSplitShade) { float overExpansionPixels = Math.max(0, h - maxPanelHeight); setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 052a4f7c5f7c..639be24ac46e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -390,6 +390,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) { mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); } + } else if (!mShowing && mBouncer.inTransit()) { + // Keyguard is not visible anymore, but expansion animation was still running. + // We need to keep propagating the expansion state to the bouncer, otherwise it will be + // stuck in transit. + mBouncer.setExpansion(fraction); } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) { // Panel expanded while pulsing but didn't translate the bouncer (because we are // unlocked.) Let's simply wake-up to dismiss the lock screen. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt index e29959290355..ca667dddbe8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.phone.panelstate /** A listener interface to be notified of state change events for the notification panel. */ -interface PanelStateListener { +fun interface PanelStateListener { /** Called when the panel's expansion state has changed. */ fun onPanelStateChanged(@PanelState state: Int) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt new file mode 100644 index 000000000000..2789db874249 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt @@ -0,0 +1,14 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import javax.inject.Inject + +/** + * An implementation on [ShadeOverScroller] that does nothing. + * + * At the moment there is only a concrete implementation [ShadeOverScroller] for split-shade, so + * this one is used when we are not in split-shade. + */ +class NoOpOverScroller @Inject constructor() : ShadeOverScroller { + override fun onPanelStateChanged(newPanelState: Int) {} + override fun onDragDownAmountChanged(newDragDownAmount: Float) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt new file mode 100644 index 000000000000..f1cedeb21e0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt @@ -0,0 +1,11 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import com.android.systemui.statusbar.phone.panelstate.PanelState + +/** Represents an over scroller for the non-lockscreen shade. */ +interface ShadeOverScroller { + + fun onPanelStateChanged(@PanelState newPanelState: Int) + + fun onDragDownAmountChanged(newDragDownAmount: Float) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt new file mode 100644 index 000000000000..2762b9a38e92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt @@ -0,0 +1,73 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import android.content.Context +import android.content.res.Configuration +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.phone.panelstate.PanelState +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** Controls the shade expansion transition on non-lockscreen. */ +@SysUISingleton +class ShadeTransitionController +@Inject +constructor( + configurationController: ConfigurationController, + panelExpansionStateManager: PanelExpansionStateManager, + private val context: Context, + private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory, + private val noOpOverScroller: NoOpOverScroller +) { + + lateinit var notificationPanelViewController: NotificationPanelViewController + lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController + lateinit var qs: QS + + private var inSplitShade = false + + private val splitShadeOverScroller by lazy { + splitShadeOverScrollerFactory.create(qs, notificationStackScrollLayoutController) + } + private val shadeOverScroller: ShadeOverScroller + get() = + if (inSplitShade && propertiesInitialized()) { + splitShadeOverScroller + } else { + noOpOverScroller + } + + init { + updateResources() + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } + }) + panelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) + panelExpansionStateManager.addStateListener(this::onPanelStateChanged) + } + + private fun updateResources() { + inSplitShade = context.resources.getBoolean(R.bool.config_use_split_notification_shade) + } + + private fun onPanelStateChanged(@PanelState state: Int) { + shadeOverScroller.onPanelStateChanged(state) + } + + private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) { + shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount) + } + + private fun propertiesInitialized() = + this::qs.isInitialized && + this::notificationPanelViewController.isInitialized && + this::notificationStackScrollLayoutController.isInitialized +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt new file mode 100644 index 000000000000..71050f2e7c67 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt @@ -0,0 +1,142 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import android.animation.Animator +import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Configuration +import android.util.MathUtils +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.R +import com.android.systemui.animation.Interpolators +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.panelstate.PanelState +import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED +import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING +import com.android.systemui.statusbar.policy.ConfigurationController +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.io.PrintWriter + +class SplitShadeOverScroller +@AssistedInject +constructor( + configurationController: ConfigurationController, + dumpManager: DumpManager, + private val context: Context, + private val scrimController: ScrimController, + @Assisted private val qS: QS, + @Assisted private val nsslController: NotificationStackScrollLayoutController +) : ShadeOverScroller { + + private var releaseOverScrollDuration = 0L + private var maxOverScrollAmount = 0 + private var previousOverscrollAmount = 0 + private var dragDownAmount: Float = 0f + @PanelState private var panelState: Int = STATE_CLOSED + private var releaseOverScrollAnimator: Animator? = null + + init { + updateResources() + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } + }) + dumpManager.registerDumpable(this::dump) + } + + private fun updateResources() { + val resources = context.resources + maxOverScrollAmount = resources.getDimensionPixelSize(R.dimen.shade_max_over_scroll_amount) + releaseOverScrollDuration = + resources.getInteger(R.integer.lockscreen_shade_over_scroll_release_duration).toLong() + } + + override fun onPanelStateChanged(@PanelState newPanelState: Int) { + if (shouldReleaseOverscroll(previousState = panelState, newState = newPanelState)) { + releaseOverScroll() + } + panelState = newPanelState + } + + override fun onDragDownAmountChanged(newDragDownAmount: Float) { + if (dragDownAmount == newDragDownAmount) { + return + } + dragDownAmount = newDragDownAmount + if (shouldOverscroll()) { + overScroll(newDragDownAmount) + } + } + + private fun shouldOverscroll() = panelState == STATE_OPENING + + private fun shouldReleaseOverscroll(@PanelState previousState: Int, @PanelState newState: Int) = + previousState == STATE_OPENING && newState != STATE_OPENING + + private fun overScroll(dragDownAmount: Float) { + val overscrollAmount: Int = calculateOverscrollAmount(dragDownAmount) + applyOverscroll(overscrollAmount) + previousOverscrollAmount = overscrollAmount + } + + private fun calculateOverscrollAmount(dragDownAmount: Float): Int { + val fullHeight: Int = nsslController.height + val fullHeightProgress: Float = MathUtils.saturate(dragDownAmount / fullHeight) + return (fullHeightProgress * maxOverScrollAmount).toInt() + } + + private fun applyOverscroll(overscrollAmount: Int) { + qS.setOverScrollAmount(overscrollAmount) + scrimController.setNotificationsOverScrollAmount(overscrollAmount) + nsslController.setOverScrollAmount(overscrollAmount) + } + + private fun releaseOverScroll() { + val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0) + animator.addUpdateListener { + val overScrollAmount = it.animatedValue as Int + qS.setOverScrollAmount(overScrollAmount) + scrimController.setNotificationsOverScrollAmount(overScrollAmount) + nsslController.setOverScrollAmount(overScrollAmount) + } + animator.interpolator = Interpolators.STANDARD + animator.duration = releaseOverScrollDuration + animator.start() + releaseOverScrollAnimator = animator + previousOverscrollAmount = 0 + } + + @VisibleForTesting + internal fun finishAnimations() { + releaseOverScrollAnimator?.end() + releaseOverScrollAnimator = null + } + + private fun dump(pw: PrintWriter, strings: Array<String>) { + pw.println( + """ + SplitShadeOverScroller: + Resources: + releaseOverScrollDuration: $releaseOverScrollDuration + maxOverScrollAmount: $maxOverScrollAmount + State: + previousOverscrollAmount: $previousOverscrollAmount + dragDownAmount: $dragDownAmount + panelState: $panelState + """.trimIndent()) + } + + @AssistedFactory + fun interface Factory { + fun create( + qS: QS, + nsslController: NotificationStackScrollLayoutController + ): SplitShadeOverScroller + } +} 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 new file mode 100644 index 000000000000..27a53bf2ceda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt @@ -0,0 +1,53 @@ +/* + * 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 8e5e1d2e1b87..5b5dca30620a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -105,25 +105,6 @@ 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 8f967ab5294f..65d501442d87 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,28 +46,35 @@ class ColorSchemeTransitionTest : SysuiTestCase() { private interface ExtractCB : (ColorScheme) -> Int private interface ApplyCB : (Int) -> Unit - private lateinit var colorTransition: ColorTransition + private lateinit var colorTransition: AnimatingColorTransition private lateinit var colorSchemeTransition: ColorSchemeTransition - @Mock private lateinit var mockTransition: ColorTransition + @Mock private lateinit var mockAnimatingTransition: AnimatingColorTransition + @Mock private lateinit var mockGenericTransition: GenericColorTransition @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 transitionFactory: ColorTransitionFactory + private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory + private lateinit var genericColorTransitionFactory: GenericColorTransitionFactory @Mock private lateinit var mediaViewHolder: MediaViewHolder @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @Before fun setUp() { - transitionFactory = { default, extractColor, applyColor -> mockTransition } + animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition } + genericColorTransitionFactory = { _ -> mockGenericTransition } whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) - colorSchemeTransition = ColorSchemeTransition(context, mediaViewHolder, transitionFactory) + colorSchemeTransition = ColorSchemeTransition( + context, mediaViewHolder, animatingColorTransitionFactory, genericColorTransitionFactory + ) - colorTransition = object : ColorTransition(DEFAULT_COLOR, extractColor, applyColor) { + colorTransition = object : AnimatingColorTransition( + DEFAULT_COLOR, extractColor, applyColor + ) { override fun buildAnimator(): ValueAnimator { return valueAnimator } @@ -142,6 +149,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() { @Test fun testColorSchemeTransition_update() { colorSchemeTransition.updateColorScheme(colorScheme) - verify(mockTransition, times(6)).updateColorScheme(colorScheme) + verify(mockAnimatingTransition, times(6)).updateColorScheme(colorScheme) + verify(mockGenericTransition).updateColorScheme(colorScheme) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 6a9c3e349522..b8c85bb41726 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -25,8 +25,12 @@ import org.mockito.Mockito.`when` as whenever import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.Icon import android.graphics.drawable.RippleDrawable @@ -124,7 +128,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var transitionParent: ViewGroup private lateinit var appIcon: ImageView - private lateinit var albumView: ImageView + @Mock private lateinit var albumView: ImageView private lateinit var titleText: TextView private lateinit var artistText: TextView private lateinit var seamless: ViewGroup @@ -296,7 +300,6 @@ public class MediaControlPanelTest : SysuiTestCase() { // Set up mock views for the players appIcon = ImageView(context) - albumView = ImageView(context) titleText = TextView(context) artistText = TextView(context) seamless = FrameLayout(context) @@ -416,7 +419,6 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(coverContainer1.context).thenReturn(mockContext) whenever(coverContainer2.context).thenReturn(mockContext) whenever(coverContainer3.context).thenReturn(mockContext) - } @After @@ -537,6 +539,60 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindAlbumView_setAfterExecutors() { + val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bmp) + canvas.drawColor(Color.RED) + val albumArt = Icon.createWithBitmap(bmp) + val state = mediaData.copy(artwork = albumArt) + + player.attachPlayer(viewHolder) + player.bindPlayer(state, PACKAGE) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + + verify(albumView).setImageDrawable(any(Drawable::class.java)) + } + + @Test + fun bindAlbumView_bitmapInLaterStates_setAfterExecutors() { + val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bmp) + canvas.drawColor(Color.RED) + val albumArt = Icon.createWithBitmap(bmp) + + val state0 = mediaData.copy(artwork = null) + val state1 = mediaData.copy(artwork = albumArt) + val state2 = mediaData.copy(artwork = albumArt) + player.attachPlayer(viewHolder) + + // First binding sets (empty) drawable + player.bindPlayer(state0, PACKAGE) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + verify(albumView).setImageDrawable(any(Drawable::class.java)) + + // Run Metadata update so that later states don't update + val captor = argumentCaptor<Animator.AnimatorListener>() + verify(mockAnimator, times(2)).addListener(captor.capture()) + captor.value.onAnimationEnd(mockAnimator) + assertThat(titleText.getText()).isEqualTo(TITLE) + assertThat(artistText.getText()).isEqualTo(ARTIST) + + // Second binding sets transition drawable + player.bindPlayer(state1, PACKAGE) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java)) + + // Third binding does run transition or update background + player.bindPlayer(state2, PACKAGE) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java)) + } + + @Test fun bind_seekBarDisabled_hasActions_seekBarVisibilityIsSetToInvisible() { useRealConstraintSets() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 0cbceb6700b4..333e148475df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock @@ -47,6 +48,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.reset +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.junit.MockitoJUnit @@ -938,6 +940,38 @@ class MediaDataManagerTest : SysuiTestCase() { eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE)) } + @Test + fun testPlaybackStateChange_keyExists_callsListener() { + // Notification has been added + addNotificationAndLoad() + val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>() + verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) + + // Callback gets an updated state + val state = PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, 0L, 1f) + .build() + callbackCaptor.value.invoke(KEY, state) + + // Listener is notified of updated state + verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), + capture(mediaDataCaptor), eq(true), eq(0), eq(false)) + assertThat(mediaDataCaptor.value.isPlaying).isTrue() + } + + @Test + fun testPlaybackStateChange_keyDoesNotExist_doesNothing() { + val state = PlaybackState.Builder().build() + val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>() + verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) + + // No media added with this key + + callbackCaptor.value.invoke(KEY, state) + verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), + anyBoolean()) + } + /** * Helper function to add a media notification and capture the resulting MediaData */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index 60cbb1754db6..91c0cc2ff891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -65,6 +65,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTimeoutLogger private lateinit var executor: FakeExecutor @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit + @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var metadataBuilder: MediaMetadata.Builder @@ -80,6 +81,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { executor = FakeExecutor(FakeSystemClock()) mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor, logger) mediaTimeoutListener.timeoutCallback = timeoutCallback + mediaTimeoutListener.stateCallback = stateCallback // Create a media session and notification for testing. metadataBuilder = MediaMetadata.Builder().apply { @@ -368,4 +370,169 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // THEN the timeout runnable is cancelled assertThat(executor.numPending()).isEqualTo(0) } + + @Test + fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() { + // Load media data once + val pausedState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When media data is loaded again, with different actions + val playingState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PLAY) + .build() + loadMediaDataWithPlaybackState(playingState) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() { + // Load media data once + val pausedState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state changes, and has different actions + val playingState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PLAY) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + + // Then the callback is invoked + verify(stateCallback).invoke(eq(KEY), eq(playingState!!)) + } + + @Test + fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() { + val customOne = PlaybackState.CustomAction.Builder( + "ACTION_1", + "custom action 1", + android.R.drawable.ic_media_ff) + .build() + val pausedState = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customOne) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state actions change + val customTwo = PlaybackState.CustomAction.Builder( + "ACTION_2", + "custom action 2", + android.R.drawable.ic_media_rew) + .build() + val pausedStateTwoActions = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customOne) + .addCustomAction(customTwo) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(pausedStateTwoActions) + + // Then the callback is invoked + verify(stateCallback).invoke(eq(KEY), eq(pausedStateTwoActions!!)) + } + + @Test + fun testOnPlaybackStateChanged_sameActions_noCallback() { + val stateWithActions = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PLAY) + .build() + loadMediaDataWithPlaybackState(stateWithActions) + + // When the playback state updates with the same actions + mediaCallbackCaptor.value.onPlaybackStateChanged(stateWithActions) + + // Then the callback is not invoked again + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnPlaybackStateChanged_sameCustomActions_noCallback() { + val actionName = "custom action" + val actionIcon = android.R.drawable.ic_media_ff + val customOne = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon) + .build() + val stateOne = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customOne) + .build() + loadMediaDataWithPlaybackState(stateOne) + + // When the playback state is updated, but has the same actions + val customTwo = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon) + .build() + val stateTwo = PlaybackState.Builder() + .setActions(PlaybackState.ACTION_PAUSE) + .addCustomAction(customTwo) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(stateTwo) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnMediaDataLoaded_isPlayingChanged_noCallback() { + // Load media data in paused state + val pausedState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PAUSED, 0L, 0f) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When media data is loaded again but playing + val playingState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, 0L, 1f) + .build() + loadMediaDataWithPlaybackState(playingState) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), any()) + } + + @Test + fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() { + // Load media data in paused state + val pausedState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PAUSED, 0L, 0f) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state changes to playing + val playingState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, 0L, 1f) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + + // Then the callback is invoked + verify(stateCallback).invoke(eq(KEY), eq(playingState!!)) + } + + @Test + fun testOnPlaybackStateChanged_isPlayingSame_noCallback() { + // Load media data in paused state + val pausedState = PlaybackState.Builder() + .setState(PlaybackState.STATE_PAUSED, 0L, 0f) + .build() + loadMediaDataWithPlaybackState(pausedState) + + // When the playback state is updated, but still not playing + val playingState = PlaybackState.Builder() + .setState(PlaybackState.STATE_STOPPED, 0L, 0f) + .build() + mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) + + // Then the callback is not invoked + verify(stateCallback, never()).invoke(eq(KEY), eq(playingState!!)) + } + + private fun loadMediaDataWithPlaybackState(state: PlaybackState) { + `when`(mediaController.playbackState).thenReturn(state) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) + } } 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/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index ce58a6c82142..64aa7fb57da8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -128,6 +128,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private val execution = FakeExecution() private val fakeParent = FrameLayout(context) private val fakePrivateLockscreenSettingUri = Uri.Builder().appendPath("test").build() + private val fakeNotifOnLockscreenSettingUri = Uri.Builder().appendPath("notif").build() private val userHandlePrimary: UserHandle = UserHandle(0) private val userHandleManaged: UserHandle = UserHandle(2) @@ -149,6 +150,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) .thenReturn(fakePrivateLockscreenSettingUri) + `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) + .thenReturn(fakeNotifOnLockscreenSettingUri) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession) `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView()) `when`(userTracker.userProfiles).thenReturn(userList) @@ -160,6 +163,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { setAllowPrivateNotifications(userHandlePrimary, true) setAllowPrivateNotifications(userHandleManaged, true) setAllowPrivateNotifications(userHandleSecondary, true) + setShowNotifications(userHandlePrimary, true) controller = LockscreenSmartspaceController( context, @@ -341,6 +345,26 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testAllTargetsAreFilteredExceptWeatherWhenNotificationsAreDisabled() { + // GIVEN the active user doesn't allow any notifications on lockscreen + setShowNotifications(userHandlePrimary, false) + connectSession() + + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary), + makeTarget(3, userHandleManaged), + makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) + ) + + sessionListener.onTargetsAvailable(targets) + + // THEN all non-sensitive content is still shown + verify(plugin).onTargetsAvailable(eq(listOf(targets[3]))) + } + + @Test fun testSensitiveTargetsAreFilteredOutForAppropriateUsers() { // GIVEN the active and managed users don't allow sensitive lockscreen content setAllowPrivateNotifications(userHandlePrimary, false) @@ -391,6 +415,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun testRecognizeSwitchToSecondaryUser() { // GIVEN an inactive secondary user that doesn't allow sensitive content setAllowPrivateNotifications(userHandleSecondary, false) + setShowNotifications(userHandleSecondary, true) connectSession() // WHEN the secondary user becomes the active user @@ -518,13 +543,15 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun makeTarget( id: Int, userHandle: UserHandle, - isSensitive: Boolean = false + isSensitive: Boolean = false, + featureType: Int = 0 ): SmartspaceTarget { return SmartspaceTarget.Builder( "target$id", ComponentName("testpackage", "testclass$id"), userHandle) .setSensitive(isSensitive) + .setFeatureType(featureType) .build() } @@ -536,6 +563,14 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { ).thenReturn(if (value) 1 else 0) } + private fun setShowNotifications(user: UserHandle, value: Boolean) { + `when`(secureSettings.getIntForUser( + eq(NOTIF_ON_LOCKSCREEN_SETTING), + anyInt(), + eq(user.identifier)) + ).thenReturn(if (value) 1 else 0) + } + private fun createSmartspaceView(): SmartspaceView { return spy(object : View(context), SmartspaceView { override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { @@ -574,3 +609,5 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private const val PRIVATE_LOCKSCREEN_SETTING = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS +private const val NOTIF_ON_LOCKSCREEN_SETTING = + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS 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/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index c9de60806b66..94a93ad6cf33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ZenModeController; @@ -135,6 +136,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private StackStateLogger mStackLogger; @Mock private NotificationStackScrollLogger mLogger; @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; + @Mock private ShadeTransitionController mShadeTransitionController; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; @@ -179,6 +181,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mNotifCollection, mEntryManager, mLockscreenShadeTransitionController, + mShadeTransitionController, mIStatusBarService, mUiEventLogger, mLayoutInflater, @@ -228,19 +231,15 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() { when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true); mController.attach(mNotificationStackScrollLayout); - verify(mSysuiStatusBarStateController).addCallback( - mStateListenerArgumentCaptor.capture(), anyInt()); - StatusBarStateController.StateListener stateListener = - mStateListenerArgumentCaptor.getValue(); - setupShowEmptyShadeViewState(stateListener, true); + setupShowEmptyShadeViewState(true); reset(mNotificationStackScrollLayout); mController.updateShowEmptyShadeView(); verify(mNotificationStackScrollLayout).updateEmptyShadeView( /* visible= */ true, /* notifVisibleInShade= */ true); - setupShowEmptyShadeViewState(stateListener, false); + setupShowEmptyShadeViewState(false); reset(mNotificationStackScrollLayout); mController.updateShowEmptyShadeView(); verify(mNotificationStackScrollLayout).updateEmptyShadeView( @@ -252,19 +251,15 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() { when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); mController.attach(mNotificationStackScrollLayout); - verify(mSysuiStatusBarStateController).addCallback( - mStateListenerArgumentCaptor.capture(), anyInt()); - StatusBarStateController.StateListener stateListener = - mStateListenerArgumentCaptor.getValue(); - setupShowEmptyShadeViewState(stateListener, true); + setupShowEmptyShadeViewState(true); reset(mNotificationStackScrollLayout); mController.updateShowEmptyShadeView(); verify(mNotificationStackScrollLayout).updateEmptyShadeView( /* visible= */ true, /* notifVisibleInShade= */ false); - setupShowEmptyShadeViewState(stateListener, false); + setupShowEmptyShadeViewState(false); reset(mNotificationStackScrollLayout); mController.updateShowEmptyShadeView(); verify(mNotificationStackScrollLayout).updateEmptyShadeView( @@ -407,15 +402,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { return argThat(new LogMatcher(category, type)); } - private void setupShowEmptyShadeViewState( - StatusBarStateController.StateListener statusBarStateListener, - boolean toShow) { + private void setupShowEmptyShadeViewState(boolean toShow) { if (toShow) { - statusBarStateListener.onStateChanged(SHADE); + when(mSysuiStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE); mController.setQsFullScreen(false); mController.getView().removeAllViews(); } else { - statusBarStateListener.onStateChanged(KEYGUARD); + when(mSysuiStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); mController.setQsFullScreen(true); mController.getView().addContainerView(mock(ExpandableNotificationRow.class)); } 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 71f1f0b0f7cf..708a5b584cbd 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 @@ -128,6 +128,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; +import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -337,6 +338,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @Mock + private ShadeTransitionController mShadeTransitionController; + @Mock private QS mQs; @Mock private View mQsHeader; @@ -352,6 +355,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 +521,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mLargeScreenShadeHeaderController, mScreenOffAnimationController, mLockscreenGestureLogger, - new PanelExpansionStateManager(), + mPanelExpansionStateManager, mNotificationRemoteInputManager, mSysUIUnfoldComponent, mControlsComponent, @@ -527,7 +532,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationListContainer, mPanelEventsEmitter, mNotificationStackSizeCalculator, - mUnlockedScreenOffAnimationController); + mUnlockedScreenOffAnimationController, + mShadeTransitionController); mNotificationPanelViewController.initDependencies( mCentralSurfaces, () -> {}, @@ -558,6 +564,37 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() { + when(mAmbientState.getFractionToShade()).thenReturn(0.5f); + mNotificationPanelViewController.setMaxDisplayedNotifications(-1); + + // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value + assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications()) + .isEqualTo(-1); + } + + @Test + public void computeMaxKeyguardNotifications_dozeAmountNotZero_returnsExistingMax() { + when(mAmbientState.getDozeAmount()).thenReturn(0.5f); + mNotificationPanelViewController.setMaxDisplayedNotifications(-1); + + // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value + assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications()) + .isEqualTo(-1); + } + + @Test + public void computeMaxKeyguardNotifications_noTransition_updatesMax() { + when(mAmbientState.getFractionToShade()).thenReturn(0f); + when(mAmbientState.getDozeAmount()).thenReturn(0f); + mNotificationPanelViewController.setMaxDisplayedNotifications(-1); + + // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value + assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications()) + .isNotEqualTo(-1); + } + + @Test public void testSetPanelScrimMinFraction() { mNotificationPanelViewController.setPanelScrimMinFraction(0.5f); verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f)); @@ -987,15 +1024,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/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index a94ad0b763aa..38e018b42985 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -192,6 +192,15 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void onPanelExpansionChanged_propagatesToBouncer_evenAfterHidden() { + mStatusBarKeyguardViewManager.hide(0, 0); + when(mBouncer.inTransit()).thenReturn(true); + + mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); + verify(mBouncer).setExpansion(eq(EXPANSION_EVENT.getFraction())); + } + + @Test public void onPanelExpansionChanged_showsBouncerWhenSwiping() { when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt new file mode 100644 index 000000000000..39d33e86925e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt @@ -0,0 +1,118 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING +import com.android.systemui.statusbar.policy.FakeConfigurationController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ShadeTransitionControllerTest : SysuiTestCase() { + + @Mock private lateinit var npvc: NotificationPanelViewController + @Mock private lateinit var nsslController: NotificationStackScrollLayoutController + @Mock private lateinit var qs: QS + @Mock private lateinit var noOpOverScroller: NoOpOverScroller + @Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller + + private lateinit var controller: ShadeTransitionController + + private val configurationController = FakeConfigurationController() + private val panelExpansionStateManager = PanelExpansionStateManager() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + controller = + ShadeTransitionController( + configurationController, + panelExpansionStateManager, + context, + splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller }, + noOpOverScroller) + + // Resetting as they are notified upon initialization. + reset(noOpOverScroller, splitShadeOverScroller) + } + + @Test + fun onPanelExpansionChanged_inSplitShade_forwardsToSplitShadeOverScroller() { + initLateProperties() + enableSplitShade() + + startPanelExpansion() + + verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING) + verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(noOpOverScroller) + } + + @Test + fun onPanelStateChanged_inSplitShade_propertiesNotInitialized_forwardsToNoOpOverScroller() { + enableSplitShade() + + startPanelExpansion() + + verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING) + verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(splitShadeOverScroller) + } + + @Test + fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() { + initLateProperties() + disableSplitShade() + + startPanelExpansion() + + verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING) + verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(splitShadeOverScroller) + } + + private fun initLateProperties() { + controller.qs = qs + controller.notificationStackScrollLayoutController = nsslController + controller.notificationPanelViewController = npvc + } + + private fun disableSplitShade() { + setSplitShadeEnabled(false) + } + + private fun enableSplitShade() { + setSplitShadeEnabled(true) + } + + private fun setSplitShadeEnabled(enabled: Boolean) { + overrideResource(R.bool.config_use_split_notification_shade, enabled) + configurationController.notifyConfigurationChanged() + } + + private fun startPanelExpansion() { + panelExpansionStateManager.onPanelExpansionChanged( + fraction = 0.5f, + expanded = true, + tracking = true, + dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT) + } + + companion object { + private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt new file mode 100644 index 000000000000..219737d1dfb4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt @@ -0,0 +1,107 @@ +package com.android.systemui.statusbar.phone.shade.transition + +import org.mockito.Mockito.`when` as whenever +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED +import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN +import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING +import com.android.systemui.statusbar.policy.FakeConfigurationController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class SplitShadeOverScrollerTest : SysuiTestCase() { + + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var scrimController: ScrimController + @Mock private lateinit var qs: QS + @Mock private lateinit var nsslController: NotificationStackScrollLayoutController + + private val configurationController = FakeConfigurationController() + private lateinit var overScroller: SplitShadeOverScroller + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(nsslController.height).thenReturn(1000) + overScroller = + SplitShadeOverScroller( + configurationController, dumpManager, context, scrimController, qs, nsslController) + } + + @Test + fun onDragDownAmountChanged_panelOpening_overScrolls_basedOnHeightAndMaxAmount() { + val maxOverScrollAmount = 50 + val dragDownAmount = 100f + overrideResource(R.dimen.shade_max_over_scroll_amount, maxOverScrollAmount) + configurationController.notifyConfigurationChanged() + + overScroller.onPanelStateChanged(STATE_OPENING) + overScroller.onDragDownAmountChanged(dragDownAmount) + + val expectedOverScrollAmount = + (dragDownAmount / nsslController.height * maxOverScrollAmount).toInt() + verify(qs).setOverScrollAmount(expectedOverScrollAmount) + verify(nsslController).setOverScrollAmount(expectedOverScrollAmount) + verify(scrimController).setNotificationsOverScrollAmount(expectedOverScrollAmount) + } + + @Test + fun onDragDownAmountChanged_panelClosed_doesNotOverScroll() { + overScroller.onPanelStateChanged(STATE_CLOSED) + overScroller.onDragDownAmountChanged(100f) + + verifyZeroInteractions(qs, scrimController, nsslController) + } + + @Test + fun onDragDownAmountChanged_panelOpen_doesNotOverScroll() { + overScroller.onPanelStateChanged(STATE_OPEN) + overScroller.onDragDownAmountChanged(100f) + + verifyZeroInteractions(qs, scrimController, nsslController) + } + + @Test + fun onPanelStateChanged_opening_thenOpen_releasesOverScroll() { + overScroller.onPanelStateChanged(STATE_OPENING) + overScroller.onDragDownAmountChanged(100f) + + overScroller.onPanelStateChanged(STATE_OPEN) + overScroller.finishAnimations() + + verify(qs, atLeastOnce()).setOverScrollAmount(0) + verify(scrimController, atLeastOnce()).setNotificationsOverScrollAmount(0) + verify(nsslController, atLeastOnce()).setOverScrollAmount(0) + } + + @Test + fun onPanelStateChanged_opening_thenClosed_releasesOverScroll() { + overScroller.onPanelStateChanged(STATE_OPENING) + overScroller.onDragDownAmountChanged(100f) + + overScroller.onPanelStateChanged(STATE_CLOSED) + overScroller.finishAnimations() + + verify(qs, atLeastOnce()).setOverScrollAmount(0) + verify(scrimController, atLeastOnce()).setNotificationsOverScrollAmount(0) + verify(nsslController, atLeastOnce()).setOverScrollAmount(0) + } +} 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/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 8c4db70c301d..aa5c501cd94e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -805,12 +805,13 @@ final class AutofillManagerServiceImpl * Updates the last fill response when a dataset was selected. */ void logDatasetSelected(@Nullable String selectedDataset, int sessionId, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, int presentationType) { synchronized (mLock) { if (isValidEventLocked("logDatasetSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null, - null, null, null, null, null, null, null)); + null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, + presentationType)); } } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 7e277ba3c0d0..f18d13d30da6 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -21,6 +21,7 @@ import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; +import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; @@ -1406,7 +1407,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override - public void fill(int requestId, int datasetIndex, Dataset dataset) { + public void fill(int requestId, int datasetIndex, Dataset dataset, int uiType) { synchronized (mLock) { if (mDestroyed) { Slog.w(TAG, "Call to Session#fill() rejected - session: " @@ -1416,7 +1417,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mHandler.sendMessage(obtainMessage( Session::autoFill, - this, requestId, datasetIndex, dataset, true)); + this, requestId, datasetIndex, dataset, true, uiType)); } // AutoFillUiCallback @@ -1657,7 +1658,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (!isAuthResultDatasetEphemeral(oldDataset, data)) { authenticatedResponse.getDatasets().set(datasetIdx, dataset); } - autoFill(requestId, datasetIdx, dataset, false); + autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN); } else { Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id " + authenticationId); @@ -3376,7 +3377,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState new InlineFillUi.InlineSuggestionUiCallback() { @Override public void autofill(@NonNull Dataset dataset, int datasetIndex) { - fill(response.getRequestId(), datasetIndex, dataset); + fill(response.getRequestId(), datasetIndex, dataset, UI_TYPE_INLINE); } @Override @@ -3895,7 +3896,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return viewState; } - void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) { + void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, + int uiType) { if (sDebug) { Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex + "; dataset=" + dataset); @@ -3909,7 +3911,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Autofill it directly... if (dataset.getAuthentication() == null) { if (generateEvent) { - mService.logDatasetSelected(dataset.getId(), id, mClientState); + mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType); } if (mCurrentViewId != null) { mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 056ab92fffb2..57768ef32391 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -15,6 +15,9 @@ */ package com.android.server.autofill.ui; +import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; +import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; + import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; @@ -31,6 +34,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.service.autofill.Dataset; +import android.service.autofill.FillEventHistory; import android.service.autofill.FillResponse; import android.service.autofill.SaveInfo; import android.service.autofill.ValueFinder; @@ -81,7 +85,8 @@ public final class AutoFillUI { public interface AutoFillUiCallback { void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent, @Nullable Bundle extras, boolean authenticateInline); - void fill(int requestId, int datasetIndex, @NonNull Dataset dataset); + void fill(int requestId, int datasetIndex, @NonNull Dataset dataset, + @FillEventHistory.Event.UiType int uiType); void save(); void cancelSave(); void requestShowFillUi(AutofillId id, int width, int height, @@ -236,7 +241,8 @@ public final class AutoFillUI { hideFillUiUiThread(callback, true); if (mCallback != null) { final int datasetIndex = response.getDatasets().indexOf(dataset); - mCallback.fill(response.getRequestId(), datasetIndex, dataset); + mCallback.fill(response.getRequestId(), datasetIndex, + dataset, UI_TYPE_MENU); } } @@ -414,7 +420,8 @@ public final class AutoFillUI { hideFillDialogUiThread(callback); if (mCallback != null) { final int datasetIndex = response.getDatasets().indexOf(dataset); - mCallback.fill(response.getRequestId(), datasetIndex, dataset); + mCallback.fill(response.getRequestId(), datasetIndex, dataset, + UI_TYPE_DIALOG); } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 730907c7d049..235dbeeb9e27 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -21,7 +21,6 @@ import static android.Manifest.permission.REQUEST_COMPANION_START_FOREGROUND_SER import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; -import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -6527,10 +6526,14 @@ public final class ActiveServices { for (int con = 0; con < crs.size(); con++) { final ConnectionRecord cr = crs.get(con); final ProcessRecord clientPr = cr.binding.client; - // Persistent process does not propagate BG-FGS-start capability - // down to service over binding. - if (clientPr.mState.getCurProcState() - <= PROCESS_STATE_PERSISTENT_UI) { + // If a binding is from a persistent process, we don't automatically + // always allow the bindee to allow FGS BG starts. In this case, + // the binder will have to explicitly make sure the bindee's + // procstate will be BFGS or above. Otherwise, for example, even if + // the system server binds to an app with BIND_NOT_FOREGROUND, + // the binder would have to be able to start FGS, which is not what + // we want. (e.g. job services shouldn't be allowed BG-FGS.) + if (clientPr.isPersistent()) { continue; } final int clientPid = clientPr.mPid; 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/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 9894a52b58e1..6e28d8fbad59 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -276,7 +276,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> AppBackgroundRestrictionsInfo.REASON_UNKNOWN, // ExemptionReason AppBackgroundRestrictionsInfo.UNKNOWN, // OptimizationLevel AppBackgroundRestrictionsInfo.SDK_UNKNOWN, // TargetSdk - isLowRamDeviceStatic()); + isLowRamDeviceStatic(), + AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN // previous RestrictionLevel + ); } } } @@ -304,11 +306,17 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND]; final double usageFgs = bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]; + final double usageForeground = + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND]; + final double usageCached = + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_CACHED]; if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) { Slog.d(TAG, "getBatteryTrackerInfoProtoLocked uid:" + uid + " allUsage:" + String.format("%4.2f%%", allUsage) + " usageBackground:" + String.format("%4.2f%%", usageBackground) - + " usageFgs:" + String.format("%4.2f%%", usageFgs)); + + " usageFgs:" + String.format("%4.2f%%", usageFgs) + + " usageForeground:" + String.format("%4.2f%%", usageForeground) + + " usageCached:" + String.format("%4.2f%%", usageCached)); } final ProtoOutputStream proto = new ProtoOutputStream(); proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_24H, @@ -317,6 +325,10 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> usageBackground * 10000); proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FGS, usageFgs * 10000); + proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FOREGROUND, + usageForeground * 10000); + proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_CACHED, + usageCached * 10000); proto.flush(); return proto.getBytes(); } 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/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 0c1ab8178b38..f7abb117e3de 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -2086,6 +2086,9 @@ public final class AppRestrictionController { int curLevel; int prevReason; final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal(); + if (trackerInfo == null) { + trackerInfo = mEmptyTrackerInfo; + } synchronized (mSettingsLock) { curLevel = getRestrictionLevel(uid, pkgName); if (curLevel == level) { @@ -2138,14 +2141,21 @@ public final class AppRestrictionController { // It's currently active, enqueue it. final int localReason = reason; final int localSubReason = subReason; - mActiveUids.add(uid, pkgName, () -> appStandbyInternal.restrictApp( - pkgName, UserHandle.getUserId(uid), localReason, localSubReason)); + final TrackerInfo localTrackerInfo = trackerInfo; + mActiveUids.add(uid, pkgName, () -> { + appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid), + localReason, localSubReason); + logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, + localTrackerInfo, localReason); + }); doIt = false; } } if (doIt) { appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid), reason, subReason); + logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo, + reason); } } } else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET @@ -2160,11 +2170,14 @@ public final class AppRestrictionController { appStandbyInternal.maybeUnrestrictApp(pkgName, UserHandle.getUserId(uid), prevReason & REASON_MAIN_MASK, prevReason & REASON_SUB_MASK, reason, subReason); + logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo, + reason); } + } - if (trackerInfo == null) { - trackerInfo = mEmptyTrackerInfo; - } + private void logAppBackgroundRestrictionInfo(String pkgName, int uid, + @RestrictionLevel int prevLevel, @RestrictionLevel int level, + @NonNull TrackerInfo trackerInfo, int reason) { FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO, uid, getRestrictionLevelStatsd(level), getThresholdStatsd(reason), @@ -2176,7 +2189,8 @@ public final class AppRestrictionController { getExemptionReasonStatsd(uid, level), getOptimizationLevelStatsd(level), getTargetSdkStatsd(pkgName), - ActivityManager.isLowRamDeviceStatic()); + ActivityManager.isLowRamDeviceStatic(), + getRestrictionLevelStatsd(prevLevel)); } private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) { @@ -2449,7 +2463,8 @@ public final class AppRestrictionController { mBgController.getBackgroundRestrictionExemptionReason(uid)), AppBackgroundRestrictionsInfo.UNKNOWN, // OptimizationLevel AppBackgroundRestrictionsInfo.SDK_UNKNOWN, // TargetSdk - ActivityManager.isLowRamDeviceStatic()); + ActivityManager.isLowRamDeviceStatic(), + mBgController.getRestrictionLevel(uid)); PendingIntent pendingIntent; if (!mBgController.mConstantsObserver.mBgPromptFgsWithNotiOnLongRunning && mBgController.hasForegroundServiceNotifications(packageName, uid)) { 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/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index a76eb8f1e55d..90b1b634f2ee 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; import android.content.Context; import android.content.Intent; import android.os.ServiceManager; +import android.os.UserHandle; import android.service.games.GameService; import android.service.games.GameSessionService; import android.service.games.IGameService; @@ -47,14 +49,17 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide @Override public GameServiceProviderInstance create( @NonNull GameServiceComponentConfiguration configuration) { + final UserHandle userHandle = configuration.getUserHandle(); + final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); return new GameServiceProviderInstanceImpl( - configuration.getUserHandle(), + userHandle, BackgroundThread.getExecutor(), mContext, - new GameClassifierImpl(mContext.getPackageManager()), + new GameTaskInfoProvider(userHandle, activityTaskManager, + new GameClassifierImpl(mContext.getPackageManager())), ActivityManager.getService(), LocalServices.getService(ActivityManagerInternal.class), - ActivityTaskManager.getService(), + activityTaskManager, (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, configuration), diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index b38195aed250..a200067b8f3f 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -64,7 +64,6 @@ import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; -import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -218,7 +217,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final UserHandle mUserHandle; private final Executor mBackgroundExecutor; private final Context mContext; - private final GameClassifier mGameClassifier; + private final GameTaskInfoProvider mGameTaskInfoProvider; private final IActivityManager mActivityManager; private final ActivityManagerInternal mActivityManagerInternal; private final IActivityTaskManager mActivityTaskManager; @@ -244,7 +243,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @NonNull UserHandle userHandle, @NonNull Executor backgroundExecutor, @NonNull Context context, - @NonNull GameClassifier gameClassifier, + @NonNull GameTaskInfoProvider gameTaskInfoProvider, @NonNull IActivityManager activityManager, @NonNull ActivityManagerInternal activityManagerInternal, @NonNull IActivityTaskManager activityTaskManager, @@ -256,7 +255,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mContext = context; - mGameClassifier = gameClassifier; + mGameTaskInfoProvider = gameTaskInfoProvider; mActivityManager = activityManager; mActivityManagerInternal = activityManagerInternal; mActivityTaskManager = activityTaskManager; @@ -344,13 +343,14 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } private void onTaskCreated(int taskId, @NonNull ComponentName componentName) { - String packageName = componentName.getPackageName(); - if (!mGameClassifier.isGame(packageName, mUserHandle)) { + final GameTaskInfo taskInfo = mGameTaskInfoProvider.get(taskId, componentName); + + if (!taskInfo.mIsGameTask) { return; } synchronized (mLock) { - gameTaskStartedLocked(taskId, componentName); + gameTaskStartedLocked(taskInfo); } } @@ -367,7 +367,17 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } final GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); - if (gameSessionRecord == null || gameSessionRecord.getGameSession() == null) { + if (gameSessionRecord == null) { + if (focused) { + // The game session for a game task may have been destroyed when the game task + // was put into the background by pressing the back button. If the task is restored + // via the Recents UI there will be no TaskStackListener#onCreated call for the + // restoration, so this focus event is the first opportunity to re-create the game + // session. + maybeCreateGameSessionForFocusedTaskLocked(taskId); + } + return; + } else if (gameSessionRecord.getGameSession() == null) { return; } @@ -379,30 +389,50 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @GuardedBy("mLock") - private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { + private void maybeCreateGameSessionForFocusedTaskLocked(int taskId) { + if (DEBUG) { + Slog.d(TAG, "maybeRecreateGameSessionForFocusedTaskLocked() id: " + taskId); + } + + final GameTaskInfo taskInfo = mGameTaskInfoProvider.get(taskId); + if (taskInfo == null) { + Slog.w(TAG, "No task info for focused task: " + taskId); + return; + } + + if (!taskInfo.mIsGameTask) { + return; + } + + gameTaskStartedLocked(taskInfo); + } + + @GuardedBy("mLock") + private void gameTaskStartedLocked(@NonNull GameTaskInfo gameTaskInfo) { if (DEBUG) { - Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); + Slog.i(TAG, "gameStartedLocked(): " + gameTaskInfo); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); + GameSessionRecord existingGameSessionRecord = mGameSessions.get(gameTaskInfo.mTaskId); if (existingGameSessionRecord != null) { - Slog.w(TAG, "Existing game session found for task (id: " + taskId + Slog.w(TAG, "Existing game session found for task (id: " + gameTaskInfo.mTaskId + ") creation. Ignoring."); return; } GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( - taskId, componentName); - mGameSessions.put(taskId, gameSessionRecord); + gameTaskInfo.mTaskId, gameTaskInfo.mComponentName); + mGameSessions.put(gameTaskInfo.mTaskId, gameSessionRecord); AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( gameService -> { gameService.gameStarted( - new GameStartedEvent(taskId, componentName.getPackageName())); + new GameStartedEvent(gameTaskInfo.mTaskId, + gameTaskInfo.mComponentName.getPackageName())); }); } @@ -769,7 +799,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @Nullable private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { - RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); + RunningTaskInfo runningTaskInfo = mGameTaskInfoProvider.getRunningTaskInfo(taskId); if (runningTaskInfo == null) { return null; } @@ -781,28 +811,6 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan bounds.height()); } - @Nullable - private RunningTaskInfo getRunningTaskInfoForTask(int taskId) { - List<RunningTaskInfo> runningTaskInfos; - try { - runningTaskInfos = mActivityTaskManager.getTasks( - /* maxNum= */ Integer.MAX_VALUE, - /* filterOnlyVisibleRecents= */ true, - /* keepIntentExtra= */ false); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to fetch running tasks"); - return null; - } - - for (RunningTaskInfo taskInfo : runningTaskInfos) { - if (taskInfo.taskId == taskId) { - return taskInfo; - } - } - - return null; - } - @VisibleForTesting void takeScreenshot(int taskId, @NonNull AndroidFuture callback) { GameSessionRecord gameSessionRecord; @@ -834,7 +842,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } else { final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle( bitmap); - final RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); + final RunningTaskInfo runningTaskInfo = + mGameTaskInfoProvider.getRunningTaskInfo(taskId); if (runningTaskInfo == null) { Slog.w(TAG, "Could not get running task info for id: " + taskId); callback.complete(GameScreenshotResult.createInternalErrorResult()); diff --git a/services/core/java/com/android/server/app/GameTaskInfo.java b/services/core/java/com/android/server/app/GameTaskInfo.java new file mode 100644 index 000000000000..7548dbd06c5c --- /dev/null +++ b/services/core/java/com/android/server/app/GameTaskInfo.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.app; + +import android.content.ComponentName; + +import java.util.Objects; + +final class GameTaskInfo { + final int mTaskId; + final boolean mIsGameTask; + final ComponentName mComponentName; + + GameTaskInfo(int taskId, boolean isGameTask, ComponentName componentName) { + mTaskId = taskId; + mIsGameTask = isGameTask; + mComponentName = componentName; + } + + @Override + public String toString() { + return "GameTaskInfo{" + + "mTaskId=" + + mTaskId + + ", mIsGameTask=" + + mIsGameTask + + ", mComponentName=" + + mComponentName + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameTaskInfo)) { + return false; + } + + GameTaskInfo that = (GameTaskInfo) o; + return mTaskId == that.mTaskId + && mIsGameTask == that.mIsGameTask + && mComponentName.equals(that.mComponentName); + } + + @Override + public int hashCode() { + return Objects.hash(mTaskId, mIsGameTask, mComponentName); + } +} diff --git a/services/core/java/com/android/server/app/GameTaskInfoProvider.java b/services/core/java/com/android/server/app/GameTaskInfoProvider.java new file mode 100644 index 000000000000..f078d98e5950 --- /dev/null +++ b/services/core/java/com/android/server/app/GameTaskInfoProvider.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.IActivityTaskManager; +import android.content.ComponentName; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.LruCache; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.List; + +final class GameTaskInfoProvider { + private static final String TAG = "GameTaskInfoProvider"; + private static final int TASK_INFO_CACHE_MAX_SIZE = 50; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final LruCache<Integer, GameTaskInfo> mGameTaskInfoCache = new LruCache<>( + TASK_INFO_CACHE_MAX_SIZE); + + private final UserHandle mUserHandle; + private final IActivityTaskManager mActivityTaskManager; + private final GameClassifier mGameClassifier; + + GameTaskInfoProvider(@NonNull UserHandle userHandle, + @NonNull IActivityTaskManager activityTaskManager, + @NonNull GameClassifier gameClassifier) { + mUserHandle = userHandle; + mActivityTaskManager = activityTaskManager; + mGameClassifier = gameClassifier; + } + + @Nullable + GameTaskInfo get(int taskId) { + synchronized (mLock) { + final GameTaskInfo cachedTaskInfo = mGameTaskInfoCache.get(taskId); + if (cachedTaskInfo != null) { + return cachedTaskInfo; + } + } + + final RunningTaskInfo runningTaskInfo = getRunningTaskInfo(taskId); + if (runningTaskInfo == null || runningTaskInfo.baseActivity == null) { + return null; + } + + return generateGameInfo(taskId, runningTaskInfo.baseActivity); + } + + GameTaskInfo get(int taskId, @NonNull ComponentName componentName) { + synchronized (mLock) { + final GameTaskInfo cachedTaskInfo = mGameTaskInfoCache.get(taskId); + if (cachedTaskInfo != null) { + if (cachedTaskInfo.mComponentName.equals(componentName)) { + Slog.w(TAG, "Found cached task info for taskId " + taskId + + " but cached component name " + cachedTaskInfo.mComponentName + + " does not match " + componentName); + } else { + return cachedTaskInfo; + } + } + } + + return generateGameInfo(taskId, componentName); + } + + @Nullable + RunningTaskInfo getRunningTaskInfo(int taskId) { + List<RunningTaskInfo> runningTaskInfos; + try { + runningTaskInfos = mActivityTaskManager.getTasks( + /* maxNum= */ Integer.MAX_VALUE, + /* filterOnlyVisibleRecents= */ false, + /* keepIntentExtra= */ false); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to fetch running tasks"); + return null; + } + + for (RunningTaskInfo taskInfo : runningTaskInfos) { + if (taskInfo.taskId == taskId) { + return taskInfo; + } + } + + return null; + } + + private GameTaskInfo generateGameInfo(int taskId, @NonNull ComponentName componentName) { + final GameTaskInfo gameTaskInfo = new GameTaskInfo(taskId, + mGameClassifier.isGame(componentName.getPackageName(), mUserHandle), componentName); + + synchronized (mLock) { + mGameTaskInfoCache.put(taskId, gameTaskInfo); + } + + return gameTaskInfo; + } +} 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/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 698f41f23e98..80ff8349a153 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1389,12 +1389,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Brightness throttling is needed, so do so quickly. // Later, when throttling is removed, we let other mechanisms decide on speed. slowChange = false; - updateScreenBrightnessSetting = true; } mAppliedThrottling = true; } else if (mAppliedThrottling) { mAppliedThrottling = false; - updateScreenBrightnessSetting = true; } if (updateScreenBrightnessSetting) { 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/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java index d175d87651de..a56380827f2c 100644 --- a/services/core/java/com/android/server/media/MediaShellCommand.java +++ b/services/core/java/com/android/server/media/MediaShellCommand.java @@ -107,7 +107,6 @@ public class MediaShellCommand extends ShellCommand { public void onHelp() { mWriter.println("usage: media_session [subcommand] [options]"); mWriter.println(" media_session dispatch KEY"); - mWriter.println(" media_session dispatch KEY"); mWriter.println(" media_session list-sessions"); mWriter.println(" media_session monitor <tag>"); mWriter.println(" media_session volume [options]"); 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 8bd1da9fbe9b..57a1fe04b690 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2545,7 +2545,6 @@ final class InstallPackageHelper { ArrayList<String>[] components; int size = 0; int[] uids; - Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPm.mLock) { final SparseArray<ArrayMap<String, ArrayList<String>>> userIdToPackagesToComponents = @@ -2584,7 +2583,6 @@ final class InstallPackageHelper { mPm.sendPackageChangedBroadcast(snapshot, packages[i], true /* dontKillApp */, components[i], uids[i], null /* reason */); } - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } void handlePackagePostInstall(PackageInstalledInfo res, InstallArgs installArgs, @@ -4189,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/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index e8faca9765f8..0dfa31c5f1fc 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -75,7 +75,7 @@ final class PackageHandler extends Handler { try { doHandleMessage(msg); } finally { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } } @@ -136,19 +136,13 @@ final class PackageHandler extends Handler { } } break; case WRITE_SETTINGS: { - Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); mPm.writeSettings(); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; case WRITE_PACKAGE_RESTRICTIONS: { - Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); mPm.writePendingRestrictions(); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; case WRITE_PACKAGE_LIST: { - Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); mPm.writePackageList(msg.arg1); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; case CHECK_PENDING_VERIFICATION: { final int verificationId = msg.arg1; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 88564aa2e935..2cef35fcf0f6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1413,7 +1413,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService t.traceBegin("create package manager"); final PackageManagerTracedLock lock = new PackageManagerTracedLock(); final Object installLock = new Object(); - HandlerThread backgroundThread = new HandlerThread("PackageManagerBg"); + + HandlerThread backgroundThread = new ServiceThread("PackageManagerBg", + Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); backgroundThread.start(); Handler backgroundHandler = new Handler(backgroundThread.getLooper()); @@ -1467,7 +1469,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService (i, pm) -> domainVerificationService, (i, pm) -> { HandlerThread thread = new ServiceThread(TAG, - Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); + Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/); thread.start(); return new PackageHandler(thread.getLooper(), pm); }, 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/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 2960bc9a3790..c0c234953297 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -420,4 +420,14 @@ class ShortcutLauncher extends ShortcutPackageItem { ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) { return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName))); } + + @Override + protected File getShortcutPackageItemFile() { + final File path = new File(mShortcutUser.mService.injectUserDataPath( + mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LUANCHERS); + // Package user id and owner id can have different values for ShortcutLaunchers. Adding + // user Id to the file name to create a unique path. Owner id is used in the root path. + final String fileName = getPackageName() + getPackageUserId() + ".xml"; + return new File(path, fileName); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index f57eaaef25a4..fef6ce1f67b3 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -160,8 +160,6 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String KEY_BITMAPS = "bitmaps"; private static final String KEY_BITMAP_BYTES = "bitmapBytes"; - private final Object mLock = new Object(); - private final Executor mExecutor; /** @@ -779,7 +777,7 @@ class ShortcutPackage extends ShortcutPackageItem { return false; } mApiCallCount++; - s.scheduleSaveUser(getOwnerUserId()); + scheduleSave(); return true; } @@ -789,7 +787,7 @@ class ShortcutPackage extends ShortcutPackageItem { } if (mApiCallCount > 0) { mApiCallCount = 0; - mShortcutUser.mService.scheduleSaveUser(getOwnerUserId()); + scheduleSave(); } } @@ -1890,15 +1888,12 @@ class ShortcutPackage extends ShortcutPackageItem { final ShortcutPackage ret = new ShortcutPackage(shortcutUser, shortcutUser.getUserId(), packageName); - synchronized (ret.mLock) { ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute( parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION; } - ret.mApiCallCount = - ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); - ret.mLastResetTime = - ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); + ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); + ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); final int outerDepth = parser.getDepth(); @@ -2440,16 +2435,15 @@ class ShortcutPackage extends ShortcutPackageItem { }))); } - void persistsAllShortcutsAsync() { - synchronized (mLock) { - final Map<String, ShortcutInfo> copy = mShortcuts; - if (!mTransientShortcuts.isEmpty()) { - copy.putAll(mTransientShortcuts); - mTransientShortcuts.clear(); - } - saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect( - Collectors.toList())); + @Override + void scheduleSaveToAppSearchLocked() { + final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts); + if (!mTransientShortcuts.isEmpty()) { + copy.putAll(mTransientShortcuts); + mTransientShortcuts.clear(); } + saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect( + Collectors.toList())); } private void saveShortcutsAsync( @@ -2548,4 +2542,12 @@ class ShortcutPackage extends ShortcutPackageItem { Binder.restoreCallingIdentity(callingIdentity); } } + + @Override + protected File getShortcutPackageItemFile() { + final File path = new File(mShortcutUser.mService.injectUserDataPath( + mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_PACKAGES); + final String fileName = getPackageName() + ".xml"; + return new File(path, fileName); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 829133c9854a..6e0436f208e3 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -23,6 +23,7 @@ import android.util.Slog; import android.util.TypedXmlSerializer; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import org.json.JSONException; @@ -36,7 +37,7 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; /** - * All methods should be guarded by {@code #mShortcutUser.mService.mLock}. + * All methods should be either guarded by {@code #mShortcutUser.mService.mLock} or {@code #mLock}. */ abstract class ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; @@ -49,6 +50,8 @@ abstract class ShortcutPackageItem { protected ShortcutUser mShortcutUser; + protected final Object mLock = new Object(); + protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, int packageUserId, @NonNull String packageName, @NonNull ShortcutPackageInfo packageInfo) { @@ -98,7 +101,7 @@ abstract class ShortcutPackageItem { } final ShortcutService s = mShortcutUser.mService; mPackageInfo.refreshSignature(s, this); - s.scheduleSaveUser(getOwnerUserId()); + scheduleSave(); } public void attemptToRestoreIfNeededAndSave() { @@ -138,7 +141,7 @@ abstract class ShortcutPackageItem { // Either way, it's no longer a shadow. mPackageInfo.setShadow(false); - s.scheduleSaveUser(mPackageUserId); + scheduleSave(); } protected abstract boolean canRestoreAnyVersion(); @@ -148,7 +151,8 @@ abstract class ShortcutPackageItem { public abstract void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException; - public void saveToFile(File path, boolean forBackup) { + @GuardedBy("mLock") + public void saveToFileLocked(File path, boolean forBackup) { final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; try { @@ -176,6 +180,11 @@ abstract class ShortcutPackageItem { } } + @GuardedBy("mLock") + void scheduleSaveToAppSearchLocked() { + + } + public JSONObject dumpCheckin(boolean clear) throws JSONException { final JSONObject result = new JSONObject(); result.put(KEY_NAME, mPackageName); @@ -187,4 +196,36 @@ abstract class ShortcutPackageItem { */ public void verifyStates() { } + + public void scheduleSave() { + mShortcutUser.mService.injectPostToHandlerDebounced( + mSaveShortcutPackageRunner, mSaveShortcutPackageRunner); + } + + private final Runnable mSaveShortcutPackageRunner = this::saveShortcutPackageItem; + + void saveShortcutPackageItem() { + // Wait for bitmap saves to conclude before proceeding to saving shortcuts. + mShortcutUser.mService.waitForBitmapSaves(); + // Save each ShortcutPackageItem in a separate Xml file. + final File path = getShortcutPackageItemFile(); + if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { + Slog.d(TAG, "Saving package item " + getPackageName() + " to " + path); + } + synchronized (mLock) { + path.getParentFile().mkdirs(); + // TODO: Since we are persisting shortcuts into AppSearch, we should read from/write to + // AppSearch as opposed to maintaining a separate XML file. + saveToFileLocked(path, false /*forBackup*/); + scheduleSaveToAppSearchLocked(); + } + } + + void removeShortcutPackageItem() { + synchronized (mLock) { + getShortcutPackageItemFile().delete(); + } + } + + protected abstract File getShortcutPackageItemFile(); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 9627c4394db7..780f976d2a40 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -748,7 +748,7 @@ public class ShortcutService extends IShortcutService.Stub { getUserShortcutsLocked(userId).cancelAllInFlightTasks(); // Save all dirty information. - saveDirtyInfo(false); + saveDirtyInfo(); // Unload mUsers.delete(userId); @@ -1203,10 +1203,6 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting void saveDirtyInfo() { - saveDirtyInfo(true); - } - - private void saveDirtyInfo(boolean saveShortcutsInAppSearch) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "saveDirtyInfo"); } @@ -1221,10 +1217,6 @@ public class ShortcutService extends IShortcutService.Stub { if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. saveBaseStateLocked(); } else { - if (saveShortcutsInAppSearch) { - getUserShortcutsLocked(userId).forAllPackages( - ShortcutPackage::persistsAllShortcutsAsync); - } saveUserLocked(userId); } } @@ -1816,7 +1808,7 @@ public class ShortcutService extends IShortcutService.Stub { } injectPostToHandlerDebounced(sp, notifyListenerRunnable(packageName, userId)); notifyShortcutChangeCallbacks(packageName, userId, changedShortcuts, removedShortcuts); - scheduleSaveUser(userId); + sp.scheduleSave(); } private void notifyListeners(@NonNull final String packageName, @UserIdInt final int userId) { @@ -2878,12 +2870,11 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutUser user = getUserShortcutsLocked(owningUserId); boolean doNotify = false; - // First, remove the package from the package list (if the package is a publisher). - if (packageUserId == owningUserId) { - if (user.removePackage(packageName) != null) { - doNotify = true; - } + final ShortcutPackage sp = (packageUserId == owningUserId) + ? user.removePackage(packageName) : null; + if (sp != null) { + doNotify = true; } // Also remove from the launcher list (if the package is a launcher). @@ -2906,6 +2897,10 @@ public class ShortcutService extends IShortcutService.Stub { // notifyListeners. user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); } + if (!appStillExists && (packageUserId == owningUserId) && sp != null) { + // If the app is removed altogether, we can get rid of the xml as well + injectPostToHandler(() -> sp.removeShortcutPackageItem()); + } if (!wasUserLoaded) { // Note this will execute the scheduled save. @@ -3788,7 +3783,7 @@ public class ShortcutService extends IShortcutService.Stub { if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { mHandler.removeCallbacks(mSaveDirtyInfoRunner); forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks); - saveDirtyInfo(false); + saveDirtyInfo(); } mShutdown.set(true); } @@ -4457,7 +4452,7 @@ public class ShortcutService extends IShortcutService.Stub { // Save to the filesystem. scheduleSaveUser(userId); - saveDirtyInfo(false); + saveDirtyInfo(); // Note, in case of backup, we don't have to wait on bitmap saving, because we don't // back up bitmaps anyway. @@ -5352,8 +5347,7 @@ public class ShortcutService extends IShortcutService.Stub { } } - @VisibleForTesting - void waitForBitmapSavesForTest() { + void waitForBitmapSaves() { synchronized (mLock) { mShortcutBitmapSaver.waitForAllSavesLocked(); } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 4bb5dcfa4b26..75e18b547c55 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -407,35 +407,10 @@ class ShortcutUser { } spi.saveToXml(out, forBackup); } else { - // Save each ShortcutPackageItem in a separate Xml file. - final File path = getShortcutPackageItemFile(spi); - if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Saving package item " + spi.getPackageName() + " to " + path); - } - - path.getParentFile().mkdirs(); - spi.saveToFile(path, forBackup); + spi.saveShortcutPackageItem(); } } - private File getShortcutPackageItemFile(ShortcutPackageItem spi) { - boolean isShortcutLauncher = spi instanceof ShortcutLauncher; - - final File path = new File(mService.injectUserDataPath(mUserId), - isShortcutLauncher ? DIRECTORY_LUANCHERS : DIRECTORY_PACKAGES); - - final String fileName; - if (isShortcutLauncher) { - // Package user id and owner id can have different values for ShortcutLaunchers. Adding - // user Id to the file name to create a unique path. Owner id is used in the root path. - fileName = spi.getPackageName() + spi.getPackageUserId() + ".xml"; - } else { - fileName = spi.getPackageName() + ".xml"; - } - - return new File(path, fileName); - } - public static ShortcutUser loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId, boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { final ShortcutUser ret = new ShortcutUser(s, userId); 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 2d7d7055a03b..62998cb6bc40 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -138,8 +138,8 @@ import android.window.ClientWindowFrames; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.ForceShowNavBarSettingsObserver; import com.android.internal.policy.GestureNavigationSettingsObserver; -import com.android.internal.policy.KidsModeSettingsObserver; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; import com.android.internal.protolog.common.ProtoLog; @@ -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. @@ -378,7 +374,7 @@ public class DisplayPolicy { private final WindowManagerInternal.AppTransitionListener mAppTransitionListener; - private final KidsModeSettingsObserver mKidsModeSettingsObserver; + private final ForceShowNavBarSettingsObserver mForceShowNavBarSettingsObserver; private boolean mForceShowNavigationBarEnabled; private class PolicyHandler extends Handler { @@ -653,17 +649,17 @@ public class DisplayPolicy { }); mHandler.post(mGestureNavigationSettingsObserver::register); - mKidsModeSettingsObserver = new KidsModeSettingsObserver( + mForceShowNavBarSettingsObserver = new ForceShowNavBarSettingsObserver( mHandler, mContext); - mKidsModeSettingsObserver.setOnChangeRunnable(() -> { + mForceShowNavBarSettingsObserver.setOnChangeRunnable(() -> { synchronized (mLock) { mForceShowNavigationBarEnabled = - mKidsModeSettingsObserver.isEnabled(); + mForceShowNavBarSettingsObserver.isEnabled(); updateSystemBarAttributes(); } }); - mForceShowNavigationBarEnabled = mKidsModeSettingsObserver.isEnabled(); - mHandler.post(mKidsModeSettingsObserver::register); + mForceShowNavigationBarEnabled = mForceShowNavBarSettingsObserver.isEnabled(); + mHandler.post(mForceShowNavBarSettingsObserver::register); } /** @@ -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 @@ -2861,7 +2844,7 @@ public class DisplayPolicy { void release() { mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener); mHandler.post(mGestureNavigationSettingsObserver::unregister); - mHandler.post(mKidsModeSettingsObserver::unregister); + mHandler.post(mForceShowNavBarSettingsObserver::unregister); mImmersiveModeConfirmation.release(); } 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/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 65ae3fcb4c90..b4029d185b9f 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -219,7 +219,7 @@ class ScreenRotationAnimation { // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of // rotation animation is an sdr image containing tone-mapping hdr content, then // disable dimming effect to get avoid of hdr content being dimmed during animation. - t.setDimmingEnabled(mScreenshotLayer, false); + t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers()); t.setLayer(mBackColorSurface, -1); t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); t.setAlpha(mBackColorSurface, 1); 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 1beb32cfdd52..56e96fa1fe58 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -591,8 +591,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) */ boolean isAllowedToBeEmbeddedInTrustedMode() { - final Predicate<ActivityRecord> callback = this::isAllowedToEmbedActivityInTrustedMode; - return forAllActivities(callback); + // Traverse all activities to see if any of them are not in the trusted mode. + final Predicate<ActivityRecord> callback = r -> !isAllowedToEmbedActivityInTrustedMode(r); + return !forAllActivities(callback); } /** @@ -2155,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 eff75bc0622b..48168a08a543 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -423,7 +423,7 @@ public class WindowManagerService extends IWindowManager.Stub "persist.wm.enable_remote_keyguard_animation"; private static final int sEnableRemoteKeyguardAnimation = - SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1); + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2); /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY @@ -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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index efed92ded095..137a3ac81b9b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -30,6 +30,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; @@ -775,6 +776,29 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } + case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT: { + final TaskFragment tf = mLaunchTaskFragments.get(hop.getContainer()); + if (tf == null || !tf.isAttached()) { + Slog.e(TAG, "Attempt to operate on detached container: " + tf); + break; + } + final ActivityRecord curFocus = tf.getDisplayContent().mFocusedApp; + if (curFocus != null && curFocus.getTaskFragment() == tf) { + Slog.d(TAG, "The requested TaskFragment already has the focus."); + break; + } + if (curFocus != null && curFocus.getTask() != tf.getTask()) { + Slog.d(TAG, "The Task of the requested TaskFragment doesn't have focus."); + break; + } + final ActivityRecord targetFocus = tf.getTopResumedActivity(); + if (targetFocus == null) { + Slog.d(TAG, "There is no resumed activity in the requested TaskFragment."); + break; + } + tf.getDisplayContent().setFocusedApp(targetFocus); + break; + } default: { // The other operations may change task order so they are skipped while in lock // task mode. The above operations are still allowed because they don't move @@ -1318,6 +1342,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: + case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT: // We are allowing organizer to start/reparent activity to a TaskFragment it // created, or set two TaskFragments adjacent to each other. Nothing to check // here because the TaskFragment may not be created yet, but will be created in @@ -1413,7 +1438,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } final WindowConfiguration requestedWindowConfig = requestedConfig.windowConfiguration; final WindowConfiguration parentWindowConfig = parentConfig.windowConfiguration; - if (!parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) { + if (!requestedWindowConfig.getBounds().isEmpty() + && !parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply bounds outside of parent for non-trusted host," @@ -1422,6 +1448,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new SecurityException(msg); } if (requestedWindowConfig.getAppBounds() != null + && !requestedWindowConfig.getAppBounds().isEmpty() && parentWindowConfig.getAppBounds() != null && !parentWindowConfig.getAppBounds().contains( requestedWindowConfig.getAppBounds())) { 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/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java index 9a0b5c7ef5ae..48a436f15803 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java @@ -69,6 +69,7 @@ class DevicePolicyData { private static final String TAG_PASSWORD_VALIDITY = "password-validity"; private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token"; private static final String TAG_PROTECTED_PACKAGES = "protected-packages"; + private static final String TAG_BYPASS_ROLE_QUALIFICATIONS = "bypass-role-qualifications"; private static final String ATTR_VALUE = "value"; private static final String ATTR_ALIAS = "alias"; private static final String ATTR_ID = "id"; @@ -107,6 +108,8 @@ class DevicePolicyData { int mPasswordOwner = -1; long mLastMaximumTimeToLock = -1; boolean mUserSetupComplete = false; + boolean mBypassDevicePolicyManagementRoleQualifications = false; + String mCurrentRoleHolder; boolean mPaired = false; int mUserProvisioningState; int mPermissionPolicy; @@ -374,6 +377,12 @@ class DevicePolicyData { out.endTag(null, TAG_APPS_SUSPENDED); } + if (policyData.mBypassDevicePolicyManagementRoleQualifications) { + out.startTag(null, TAG_BYPASS_ROLE_QUALIFICATIONS); + out.attribute(null, ATTR_VALUE, policyData.mCurrentRoleHolder); + out.endTag(null, TAG_BYPASS_ROLE_QUALIFICATIONS); + } + out.endTag(null, "policies"); out.endDocument(); @@ -558,6 +567,9 @@ class DevicePolicyData { } else if (TAG_APPS_SUSPENDED.equals(tag)) { policy.mAppsSuspended = parser.getAttributeBoolean(null, ATTR_VALUE, false); + } else if (TAG_BYPASS_ROLE_QUALIFICATIONS.equals(tag)) { + policy.mBypassDevicePolicyManagementRoleQualifications = true; + policy.mCurrentRoleHolder = parser.getAttributeValue(null, ATTR_VALUE); } else { Slogf.w(TAG, "Unknown tag: %s", tag); XmlUtils.skipCurrentTag(parser); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e2d8a63fbfda..0a426eb80c54 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -138,7 +138,6 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.provider.Settings.Global.BYPASS_DEVICE_POLICY_MANAGEMENT_ROLE_QUALIFICATIONS; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -222,6 +221,7 @@ import android.app.admin.UnsafeStateException; import android.app.admin.WifiSsidPolicy; import android.app.backup.IBackupManager; import android.app.compat.CompatChanges; +import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.app.trust.TrustManager; import android.app.usage.UsageStatsManagerInternal; @@ -255,6 +255,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.Signature; import android.content.pm.StringParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Resources; @@ -409,6 +410,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; @@ -756,6 +758,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL; private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider; + private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver; private static final boolean ENABLE_LOCK_GUARD = true; @@ -1823,6 +1826,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector); mDeviceManagementResourcesProvider = mInjector.getDeviceManagementResourcesProvider(); + mDevicePolicyManagementRoleObserver = new DevicePolicyManagementRoleObserver(mContext); + mDevicePolicyManagementRoleObserver.register(); // "Lite" interface is available even when the device doesn't have the feature LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService); @@ -13052,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; } @@ -18663,16 +18670,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( android.Manifest.permission.MANAGE_ROLE_HOLDERS)); return mInjector.binderWithCleanCallingIdentity(() -> { - if (mInjector.settingsGlobalGetInt( - BYPASS_DEVICE_POLICY_MANAGEMENT_ROLE_QUALIFICATIONS, /* def= */ 0) == 1) { - return true; - } - if (shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal()) { - mInjector.settingsGlobalPutInt( - BYPASS_DEVICE_POLICY_MANAGEMENT_ROLE_QUALIFICATIONS, /* value= */ 1); + if (getUserData( + UserHandle.USER_SYSTEM).mBypassDevicePolicyManagementRoleQualifications) { return true; } - return false; + return shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal(); }); } @@ -18685,6 +18687,142 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return accounts.length == 0; } + private void setBypassDevicePolicyManagementRoleQualificationStateInternal( + String currentRoleHolder, boolean allowBypass) { + boolean stateChanged = false; + DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM); + if (policy.mBypassDevicePolicyManagementRoleQualifications != allowBypass) { + policy.mBypassDevicePolicyManagementRoleQualifications = allowBypass; + stateChanged = true; + } + if (!Objects.equals(currentRoleHolder, policy.mCurrentRoleHolder)) { + policy.mCurrentRoleHolder = currentRoleHolder; + stateChanged = true; + } + if (stateChanged) { + synchronized (getLockObject()) { + saveSettingsLocked(UserHandle.USER_SYSTEM); + } + } + } + + private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener { + private RoleManager mRm; + private final Executor mExecutor; + private final Context mContext; + + DevicePolicyManagementRoleObserver(@NonNull Context context) { + mContext = context; + mExecutor = mContext.getMainExecutor(); + mRm = mContext.getSystemService(RoleManager.class); + } + + public void register() { + mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.SYSTEM); + } + + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { + if (!RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) { + return; + } + String newRoleHolder = getRoleHolder(); + if (isDefaultRoleHolder(newRoleHolder)) { + Slogf.i(LOG_TAG, + "onRoleHoldersChanged: Default role holder is set, returning early"); + return; + } + if (newRoleHolder == null) { + Slogf.i(LOG_TAG, + "onRoleHoldersChanged: New role holder is null, returning early"); + return; + } + if (shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal()) { + Slogf.w(LOG_TAG, + "onRoleHoldersChanged: Updating current role holder to " + newRoleHolder); + setBypassDevicePolicyManagementRoleQualificationStateInternal( + newRoleHolder, /* allowBypass= */ true); + return; + } + DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM); + if (!newRoleHolder.equals(policy.mCurrentRoleHolder)) { + Slogf.w(LOG_TAG, + "onRoleHoldersChanged: You can't set a different role holder, role " + + "is getting revoked from " + newRoleHolder); + setBypassDevicePolicyManagementRoleQualificationStateInternal( + /* currentRoleHolder= */ null, /* allowBypass= */ false); + mRm.removeRoleHolderAsUser( + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, + newRoleHolder, + /* flags= */ 0, + user, + mExecutor, + successful -> {}); + } + } + + private String getRoleHolder() { + return DevicePolicyManagerService.this.getDevicePolicyManagementRoleHolderPackageName( + mContext); + } + + private boolean isDefaultRoleHolder(String packageName) { + String defaultRoleHolder = getDefaultRoleHolderPackageName(); + if (packageName == null || defaultRoleHolder == null) { + return false; + } + if (!defaultRoleHolder.equals(packageName)) { + return false; + } + return hasSigningCertificate( + packageName, getDefaultRoleHolderPackageSignature()); + } + + private boolean hasSigningCertificate(String packageName, String certificateString) { + if (packageName == null || certificateString == null) { + return false; + } + byte[] certificate; + try { + certificate = new Signature(certificateString).toByteArray(); + } catch (IllegalArgumentException e) { + Slogf.w(LOG_TAG, "Cannot parse signing certificate: " + certificateString, e); + return false; + } + PackageManager pm = mInjector.getPackageManager(); + return pm.hasSigningCertificate( + packageName, certificate, PackageManager.CERT_INPUT_SHA256); + } + + private String getDefaultRoleHolderPackageName() { + String[] info = getDefaultRoleHolderPackageNameAndSignature(); + if (info == null) { + return null; + } + return info[0]; + } + + private String getDefaultRoleHolderPackageSignature() { + String[] info = getDefaultRoleHolderPackageNameAndSignature(); + if (info == null || info.length < 2) { + return null; + } + return info[1]; + } + + private String[] getDefaultRoleHolderPackageNameAndSignature() { + String packageNameAndSignature = mContext.getString( + com.android.internal.R.string.config_devicePolicyManagement); + if (TextUtils.isEmpty(packageNameAndSignature)) { + return null; + } + if (packageNameAndSignature.contains(":")) { + return packageNameAndSignature.split(":"); + } + return new String[]{packageNameAndSignature}; + } + } + @Override public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( 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/Android.bp b/services/tests/mockingservicestests/Android.bp index 670c1596e15e..08c68b9a469e 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -42,6 +42,8 @@ android_test { static_libs: [ "androidx.test.core", "androidx.test.runner", + "androidx.test.espresso.core", + "androidx.test.espresso.contrib", "androidx.test.ext.truth", "frameworks-base-testutils", "hamcrest-library", diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index 7714cf0ca094..07b763dcd85b 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -32,6 +32,8 @@ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <uses-permission android:name="android.permission.MANAGE_GAME_ACTIVITY" /> + <uses-permission android:name="android.permission.SET_ALWAYS_FINISH" /> <!-- needed by MasterClearReceiverTest to display a system dialog --> <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/> @@ -39,6 +41,8 @@ <application android:testOnly="true" android:debuggable="true"> <uses-library android:name="android.test.runner" /> + <activity + android:name="android.service.games.GameSessionTrampolineActivityTest$TestActivity" /> </application> <instrumentation diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java new file mode 100644 index 000000000000..d68b517ca8cd --- /dev/null +++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java @@ -0,0 +1,212 @@ +/* + * 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 android.service.games; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isClickable; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.ext.truth.content.IntentSubject.assertThat; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.hamcrest.Matchers.allOf; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Intent; +import android.os.Bundle; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.test.espresso.NoActivityResumedException; +import androidx.test.filters.SmallTest; + +import com.android.internal.infra.AndroidFuture; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +/** + * Unit tests for the {@link GameSessionTrampolineActivity}. + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@Presubmit +public class GameSessionTrampolineActivityTest { + + @Before + public void setUp() { + setAlwaysFinishActivities(false); + } + + @After + public void tearDown() { + setAlwaysFinishActivities(false); + } + + @Test + public void launch_launchesTargetActivity() { + AndroidFuture<GameSessionActivityResult> unusedResultFuture = + startTestActivityViaGameSessionTrampolineActivity(); + + TestActivityPage.assertPageIsLaunched(); + } + + @Test + public void launch_targetActivityFinishesSuccessfully_futureCompletedWithSameResults() { + AndroidFuture<GameSessionActivityResult> resultFuture = + startTestActivityViaGameSessionTrampolineActivity(); + + TestActivityPage.assertPageIsLaunched(); + TestActivityPage.clickFinish(); + + GameSessionActivityResult expectedResult = + new GameSessionActivityResult(Activity.RESULT_OK, TestActivity.RESULT_INTENT); + + assertEquals(resultFuture, expectedResult); + + TestActivityPage.assertPageIsNotLaunched(); + } + + @Test + public void launch_trampolineActivityProcessDeath_futureCompletedWithSameResults() { + setAlwaysFinishActivities(true); + + AndroidFuture<GameSessionActivityResult> resultFuture = + startTestActivityViaGameSessionTrampolineActivity(); + + TestActivityPage.assertPageIsLaunched(); + TestActivityPage.clickFinish(); + + GameSessionActivityResult expectedResult = + new GameSessionActivityResult(Activity.RESULT_OK, TestActivity.RESULT_INTENT); + + assertEquals(resultFuture, expectedResult); + + TestActivityPage.assertPageIsNotLaunched(); + } + + private static void assertEquals( + AndroidFuture<GameSessionActivityResult> actualFuture, + GameSessionActivityResult expected) { + try { + assertEquals(actualFuture.get(20, TimeUnit.SECONDS), expected); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private static void assertEquals( + GameSessionActivityResult actual, + GameSessionActivityResult expected) { + assertThat(actual.getResultCode()).isEqualTo(expected.getResultCode()); + assertThat(actual.getData()).filtersEquallyTo(actual.getData()); + } + + private static void setAlwaysFinishActivities(boolean isEnabled) { + try { + ActivityManager.getService().setAlwaysFinish(isEnabled); + } catch (RemoteException ex) { + throw new IllegalStateException(ex); + } + } + + private static AndroidFuture<GameSessionActivityResult> + startTestActivityViaGameSessionTrampolineActivity() { + Intent testActivityIntent = new Intent(); + testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class); + + return startGameSessionTrampolineActivity(testActivityIntent); + } + + private static AndroidFuture<GameSessionActivityResult> startGameSessionTrampolineActivity( + Intent targetIntent) { + AndroidFuture<GameSessionActivityResult> resultFuture = new AndroidFuture<>(); + Intent trampolineActivityIntent = GameSessionTrampolineActivity.createIntent(targetIntent, + null, resultFuture); + trampolineActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getInstrumentation().getTargetContext().startActivity(trampolineActivityIntent); + getInstrumentation().waitForIdleSync(); + + return resultFuture; + } + + + private static class TestActivityPage { + private TestActivityPage() {} + + public static void assertPageIsLaunched() { + onView(withText(TestActivity.PAGE_TITLE_TEXT)).check(matches(isDisplayed())); + } + + public static void assertPageIsNotLaunched() { + try { + onView(withText(TestActivity.PAGE_TITLE_TEXT)).check(doesNotExist()); + } catch (NoActivityResumedException ex) { + // Do nothing + } + } + + public static void clickFinish() { + onView(allOf(withText(TestActivity.FINISH_BUTTON_TEXT), isClickable())).perform( + click()); + getInstrumentation().waitForIdleSync(); + } + } + + public static class TestActivity extends Activity { + private static final String PAGE_TITLE_TEXT = "GameSessionTestActivity"; + private static final String FINISH_BUTTON_TEXT = "Finish Test Activity"; + private static final Intent RESULT_INTENT = new Intent("com.test.action.VIEW"); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout contentLayout = new LinearLayout(this); + contentLayout.setOrientation(LinearLayout.VERTICAL); + + TextView titleTextView = new TextView(this); + titleTextView.setText(PAGE_TITLE_TEXT); + contentLayout.addView(titleTextView); + + Button finishActivityButton = new Button(this); + finishActivityButton.setText(FINISH_BUTTON_TEXT); + finishActivityButton.setOnClickListener((unused) -> { + setResult(Activity.RESULT_OK, RESULT_INTENT); + finish(); + }); + + + contentLayout.addView(finishActivityButton); + setContentView(contentLayout); + } + } +} 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/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index 319a769bb1de..5b551b1c183c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -216,11 +216,12 @@ public final class GameServiceProviderInstanceImplTest { mRunningTaskInfos); + final UserHandle userHandle = new UserHandle(USER_ID); mGameServiceProviderInstance = new GameServiceProviderInstanceImpl( - new UserHandle(USER_ID), + userHandle, ConcurrentUtils.DIRECT_EXECUTOR, mMockContext, - mFakeGameClassifier, + new GameTaskInfoProvider(userHandle, mMockActivityTaskManager, mFakeGameClassifier), mMockActivityManager, mMockActivityManagerInternal, mMockActivityTaskManager, @@ -788,6 +789,36 @@ public final class GameServiceProviderInstanceImplTest { } @Test + public void gameTaskFocusedWithCreateAfterRemoved_gameSessionRecreated() throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + stopTask(10); + + assertThat(gameSession10.mIsDestroyed).isTrue(); + + // If the game task is restored via the Recents UI, the task will be running again but + // we would not expect any call to TaskStackListener#onTaskCreated. + addRunningTaskInfo(10, GAME_A_MAIN_ACTIVITY); + + // We now receive a task focused event for the task. This will occur if the game task is + // restored via the Recents UI. + dispatchTaskFocused(10, /*focused=*/ true); + mFakeGameService.requestCreateGameSession(10); + + // Verify that a new pending game session is created for the game's taskId. + assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10)); + } + + @Test public void gameTaskRemoved_removesTaskOverlay() throws Exception { mGameServiceProviderInstance.start(); @@ -1144,13 +1175,18 @@ public final class GameServiceProviderInstanceImplTest { } private void startTask(int taskId, ComponentName componentName) { + addRunningTaskInfo(taskId, componentName); + + dispatchTaskCreated(taskId, componentName); + } + + private void addRunningTaskInfo(int taskId, ComponentName componentName) { RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); runningTaskInfo.taskId = taskId; + runningTaskInfo.baseActivity = componentName; runningTaskInfo.displayId = 1; runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800)); mRunningTaskInfos.add(runningTaskInfo); - - dispatchTaskCreated(taskId, componentName); } private void stopTask(int taskId) { 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/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 449177ef9b7d..0afb1829f9d4 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -102,6 +102,7 @@ <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> + <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> 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/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java index 0e84e044e44b..42c4129513d0 100644 --- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java @@ -16,6 +16,10 @@ package com.android.server; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.app.job.JobScheduler; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -43,13 +47,15 @@ public class BinaryTransparencyServiceTest { @Before public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); + mContext = spy(ApplicationProvider.getApplicationContext()); mBinaryTransparencyService = new BinaryTransparencyService(mContext); mTestInterface = mBinaryTransparencyService.new BinaryTransparencyServiceImpl(); } private void prepSignedInfo() { // simulate what happens on boot completed phase + // but we avoid calling JobScheduler.schedule by returning a null. + doReturn(null).when(mContext).getSystemService(JobScheduler.class); mBinaryTransparencyService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); } 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/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 2cf67f83578b..e991ec6879ae 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -237,6 +237,8 @@ public class DpmMockContext extends MockContext { return mMockSystemServices.devicePolicyManager; case Context.LOCATION_SERVICE: return mMockSystemServices.locationManager; + case Context.ROLE_SERVICE: + return mMockSystemServices.roleManager; } throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 34c9f7c2ef87..884ffce155d7 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -33,6 +33,7 @@ import android.app.IActivityTaskManager; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; import android.app.backup.IBackupManager; +import android.app.role.RoleManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -131,6 +132,7 @@ public class MockSystemServices { public final VpnManager vpnManager; public final DevicePolicyManager devicePolicyManager; public final LocationManager locationManager; + public final RoleManager roleManager; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; public final BuildMock buildMock = new BuildMock(); @@ -181,6 +183,7 @@ public class MockSystemServices { vpnManager = mock(VpnManager.class); devicePolicyManager = mock(DevicePolicyManager.class); locationManager = mock(LocationManager.class); + roleManager = realContext.getSystemService(RoleManager.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(realContext.getPackageManager()); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index bf3c7c3e05fb..d5612e7a27b2 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -38,6 +39,7 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.BrightnessConfiguration; @@ -72,6 +74,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; @@ -93,6 +96,7 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.time.Duration; @@ -203,6 +207,10 @@ public class DisplayManagerServiceTest { @Test public void testCreateVirtualDisplay_sentToInputManager() { + // This is to update the display device config such that DisplayManagerService can ignore + // the usage of SensorManager, which is available only after the PowerManagerService + // is ready. + resetConfigToIgnoreSensorManager(mContext); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); registerDefaultDisplays(displayManager); @@ -275,6 +283,10 @@ public class DisplayManagerServiceTest { @Test public void testPhysicalViewports() { + // This is to update the display device config such that DisplayManagerService can ignore + // the usage of SensorManager, which is available only after the PowerManagerService + // is ready. + resetConfigToIgnoreSensorManager(mContext); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); registerDefaultDisplays(displayManager); @@ -1343,6 +1355,20 @@ public class DisplayManagerServiceTest { } } + private void resetConfigToIgnoreSensorManager(Context context) { + final Resources res = Mockito.spy(context.getResources()); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_ambientThresholdsOfPeakRefreshRate); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_brightnessThresholdsOfPeakRefreshRate); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_highDisplayBrightnessThresholdsOfFixedRefreshRate); + doReturn(new int[]{-1}).when(res).getIntArray(R.array + .config_highAmbientBrightnessThresholdsOfFixedRefreshRate); + + when(context.getResources()).thenReturn(res); + } + private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub { int mDisplayId; boolean mDisplayAddedCalled = false; diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING new file mode 100644 index 000000000000..9f1a209d2ee1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.display." + }, + { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} 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/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index fdf9354747a0..40943774c0af 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -1975,7 +1975,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (si == null) { return null; } - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); return new File(si.getBitmapPath()).getName(); } @@ -1984,7 +1984,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (si == null) { return null; } - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); return new File(si.getBitmapPath()).getAbsolutePath(); } @@ -2139,7 +2139,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } protected boolean bitmapDirectoryExists(String packageName, int userId) { - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); final File path = new File(mService.getUserBitmapFilePath(userId), packageName); return path.isDirectory(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 867890f938ba..411b52155abb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1040,7 +1040,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { dumpsysOnLogcat(); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); // Check files and directories. // Package 3 has no bitmaps, so we don't create a directory. assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2); @@ -1096,7 +1096,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile(); makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile(); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3, "a.b.c", "d.e.f"); @@ -1111,7 +1111,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3 // directory. - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3); assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2); @@ -1390,7 +1390,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithContentUri("test_uri")) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconUri()); @@ -1402,13 +1402,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconResource()); assertEquals(R.drawable.black_32x32, si.getIconResourceId()); }); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); mInjectedCurrentTimeMillis += INTERVAL; // reset throttling @@ -1419,7 +1419,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getTestContext().getResources(), R.drawable.black_64x64))) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconFile()); @@ -1437,7 +1437,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getTestContext().getResources(), R.drawable.black_64x64))) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconFile()); @@ -1451,7 +1451,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconResource()); @@ -1463,7 +1463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithContentUri("test_uri")) .build() ))); - mService.waitForBitmapSavesForTest(); + mService.waitForBitmapSaves(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconUri()); 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/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 4425962eb8eb..7a704742fba2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -522,6 +522,55 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_requestFocusOnTaskFragment() { + mOrganizer.applyTransaction(mTransaction); + mController.registerOrganizer(mIOrganizer); + final Task task = createTask(mDisplayContent); + final IBinder token0 = new Binder(); + final TaskFragment tf0 = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(token0) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + final IBinder token1 = new Binder(); + final TaskFragment tf1 = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(token1) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + mAtm.mWindowOrganizerController.mLaunchTaskFragments.put(token0, tf0); + mAtm.mWindowOrganizerController.mLaunchTaskFragments.put(token1, tf1); + final ActivityRecord activity0 = tf0.getTopMostActivity(); + final ActivityRecord activity1 = tf1.getTopMostActivity(); + + // No effect if the current focus is in a different Task. + final ActivityRecord activityInOtherTask = createActivityRecord(mDefaultDisplay); + mDisplayContent.setFocusedApp(activityInOtherTask); + mTransaction.requestFocusOnTaskFragment(token0); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + assertEquals(activityInOtherTask, mDisplayContent.mFocusedApp); + + // No effect if there is no resumed activity in the request TaskFragment. + activity0.setState(ActivityRecord.State.PAUSED, "test"); + activity1.setState(ActivityRecord.State.RESUMED, "test"); + mDisplayContent.setFocusedApp(activity1); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + assertEquals(activity1, mDisplayContent.mFocusedApp); + + // Set focus to the request TaskFragment when the current focus is in the same Task, and it + // has a resumed activity. + activity0.setState(ActivityRecord.State.RESUMED, "test"); + mDisplayContent.setFocusedApp(activity1); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + assertEquals(activity0, mDisplayContent.mFocusedApp); + } + + @Test public void testTaskFragmentInPip_startActivityInTaskFragment() { setupTaskFragmentInPip(); final ActivityRecord activity = mTaskFragment.getTopMostActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 3c14777cd7d1..b2043c38d00a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -403,4 +403,29 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(activity0.hasOverlayOverUntrustedModeEmbedded()); assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded()); } + + @Test + public void testIsAllowedToBeEmbeddedInTrustedMode() { + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setCreateParentTask() + .createActivityCount(2) + .build(); + final ActivityRecord activity0 = taskFragment.getBottomMostActivity(); + final ActivityRecord activity1 = taskFragment.getTopMostActivity(); + + // Allowed if all children activities are allowed. + doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0); + doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1); + + assertTrue(taskFragment.isAllowedToBeEmbeddedInTrustedMode()); + + // Disallowed if any child activity is not allowed. + doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0); + + assertFalse(taskFragment.isAllowedToBeEmbeddedInTrustedMode()); + + doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1); + + assertFalse(taskFragment.isAllowedToBeEmbeddedInTrustedMode()); + } } 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/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8cbbe947d32d..f31cdcba5830 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1824,6 +1824,32 @@ public class VoiceInteractionManagerService extends SystemService { } } + public void setSessionWindowVisible(IBinder token, boolean visible) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "setSessionWindowVisible called without running voice interaction " + + "service"); + return; + } + if (mImpl.mActiveSession == null || token != mImpl.mActiveSession.mToken) { + Slog.w(TAG, "setSessionWindowVisible does not match active session"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mVoiceInteractionSessionListeners.broadcast(listener -> { + try { + listener.onVoiceSessionWindowVisibilityChanged(visible); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering window visibility event to listener.", e); + } + }); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 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; diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index c56cc62abe44..235ed842b749 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -1293,8 +1293,8 @@ public class ApnSetting implements Parcelable { && Objects.equals(this.mOperatorNumeric, other.mOperatorNumeric) && Objects.equals(this.mProtocol, other.mProtocol) && Objects.equals(this.mRoamingProtocol, other.mRoamingProtocol) - && xorEqualsInt(this.mMtuV4, other.mMtuV4) - && xorEqualsInt(this.mMtuV6, other.mMtuV6) + && mtuUnsetOrEquals(this.mMtuV4, other.mMtuV4) + && mtuUnsetOrEquals(this.mMtuV6, other.mMtuV6) && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled) && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask) && Objects.equals(this.mLingeringNetworkTypeBitmask, @@ -1322,7 +1322,12 @@ public class ApnSetting implements Parcelable { // Equal or one is not specified. private boolean xorEqualsInt(int first, int second) { return first == UNSPECIFIED_INT || second == UNSPECIFIED_INT - || Objects.equals(first, second); + || first == second; + } + + // Equal or one is not specified. Specific to MTU where <= 0 indicates unset. + private boolean mtuUnsetOrEquals(int first, int second) { + return first <= 0 || second <= 0 || first == second; } private String nullToEmpty(String stringValue) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index 93b987ea8787..aacc17a49a24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -16,6 +16,10 @@ package com.android.server.wm.flicker.helpers +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars + import android.app.Instrumentation import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice @@ -25,6 +29,8 @@ import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import java.util.regex.Pattern + class ImeAppAutoFocusHelper @JvmOverloads constructor( instr: Instrumentation, private val rotation: Int, @@ -72,6 +78,7 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( wmHelper.waitForAppTransitionIdle() wmHelper.waitForFullScreenApp( ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) + mInstrumentation.waitForIdleSync() } fun dismissDialog(wmHelper: WindowManagerStateHelper) { val dialog = uiDevice.wait( @@ -83,4 +90,27 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( wmHelper.waitForAppTransitionIdle() } } + fun getInsetsVisibleFromDialog(type: Int): Boolean { + var insetsVisibilityTextView = uiDevice.wait( + Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) + if (insetsVisibilityTextView != null) { + var visibility = insetsVisibilityTextView.text.toString() + val matcher = when (type) { + ime() -> { + Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + statusBars() -> { + Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + navigationBars() -> { + Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + else -> null + } + if (matcher != null && matcher.find()) { + return matcher.group(1).equals("VISIBLE") + } + } + return false + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt index 1b60403ac354..2f8f9441a7b9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt @@ -16,6 +16,10 @@ package com.android.server.wm.flicker.ime +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars + import android.app.Instrumentation import android.platform.test.annotations.Presubmit import android.view.Surface @@ -35,6 +39,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue /** * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. @@ -56,6 +62,10 @@ class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestPar testApp.launchViaIntent(wmHelper) wmHelper.waitImeShown() testApp.startDialogThemedActivity(wmHelper) + // Verify IME insets isn't visible on dialog since it's non-IME focusable window + assertFalse(testApp.getInsetsVisibleFromDialog(ime())) + assertTrue(testApp.getInsetsVisibleFromDialog(statusBars())) + assertTrue(testApp.getInsetsVisibleFromDialog(navigationBars())) } } teardown { diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java index 27606d81f9d3..20eb295d3e6b 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java @@ -17,11 +17,16 @@ package com.android.server.wm.flicker.testapp; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import android.app.Activity; import android.app.AlertDialog; +import android.app.Dialog; import android.graphics.Color; import android.os.Bundle; import android.view.WindowManager; @@ -33,9 +38,12 @@ public class DialogThemedActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple); + getWindow().addFlags(FLAG_NOT_FOCUSABLE); getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT); TextView textView = new TextView(this); - textView.setText("This is a test dialog"); + // Print SystemBars' insets visibility on this window for demonstrating during the test. + textView.setId(android.R.id.text1); + textView.setText("Insets visibility\n\n"); textView.setTextColor(Color.BLACK); LinearLayout layout = new LinearLayout(this); layout.setBackgroundColor(Color.GREEN); @@ -51,7 +59,17 @@ public class DialogThemedActivity extends Activity { attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM; dialog.getWindow().getDecorView().setLayoutParams(attrs); dialog.setCanceledOnTouchOutside(true); + dialog.setOnShowListener(d -> textView.setText(textView.getText() + + "IME: " + isInsetsVisible(dialog, ime()) + "\n" + + "StatusBar: " + isInsetsVisible(dialog, statusBars()) + "\n" + + "NavBar: " + isInsetsVisible(dialog, navigationBars()) + "\n") + ); dialog.show(); dialog.setOnDismissListener((d) -> finish()); } + + private String isInsetsVisible(Dialog d, int type) { + return d.getWindow().getDecorView().getRootWindowInsets().isVisible(type) ? "VISIBLE" + : "INVISIBLE"; + } } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java index 90fd08bae4ef..47f87d6d75ff 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java @@ -95,6 +95,8 @@ public final class NotificationTest { PackageManager pm = mContext.getPackageManager(); // Do not run on Automotive. assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); + // Do not run on TV. Direct Reply isn't supported on TV. + assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)); } @After |