diff options
106 files changed, 2968 insertions, 822 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index dd0d07f68547..c3fc7b16ebdf 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -16,8 +16,10 @@ package android.app.tare; +import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; +import android.util.Log; /** * Provides access to the resource economy service. @@ -26,6 +28,69 @@ import android.content.Context; */ @SystemService(Context.RESOURCE_ECONOMY_SERVICE) public class EconomyManager { + private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName(); + + /** + * 1 ARC = 1 GIGA-CAKE! + * + * @hide + */ + public static final long CAKE_IN_ARC = 1_000_000_000L; + + /** @hide */ + public static long arcToCake(int arcs) { + return arcs * CAKE_IN_ARC; + } + + /** + * Parses a configuration string to get the value in cakes. + * + * @hide + */ + public static long parseCreditValue(@Nullable final String val, final long defaultValCakes) { + String trunc; + if (val == null || (trunc = val.trim()).isEmpty()) { + return defaultValCakes; + } + long multiplier; + if (trunc.endsWith("c")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier = 1; + } else if (trunc.endsWith("ck")) { + trunc = trunc.substring(0, trunc.length() - 2); + multiplier = 1; + } else if (trunc.endsWith("A")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier = CAKE_IN_ARC; + } else if (trunc.endsWith("ARC")) { + trunc = trunc.substring(0, trunc.length() - 3); + multiplier = CAKE_IN_ARC; + } else { + // Don't risk using the wrong units + Log.e(TAG, "Couldn't determine units of credit value: " + val); + return defaultValCakes; + } + + // Allow people to shorten notation (eg. Mc for Megacake). + if (trunc.endsWith("k")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier *= 1_000; + } else if (trunc.endsWith("M")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier *= 1_000_000; + } else if (trunc.endsWith("G")) { + trunc = trunc.substring(0, trunc.length() - 1); + multiplier *= 1_000_000_000; + } + + try { + return Long.parseLong(trunc) * multiplier; + } catch (NumberFormatException e) { + Log.e(TAG, "Malformed config string: " + val + " to " + trunc, e); + return defaultValCakes; + } + } + // Keys for AlarmManager TARE factors /** @hide */ public static final String KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED = @@ -276,179 +341,201 @@ public class EconomyManager { // Default values AlarmManager factors /** @hide */ - public static final int DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED = 500; + public static final long DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 200; + public static final long DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES = + arcToCake(256); /** @hide */ - public static final int DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP = 160; + public static final long DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES = arcToCake(160); /** @hide */ - public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440; + public static final long DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES = arcToCake(960); /** @hide */ - public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 4000; + public static final long DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(2880); /** @hide */ - public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 28_800; + public static final long DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000); // TODO: add AlarmManager modifier default values /** @hide */ - public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0; + public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0); /** @hide */ - public static final float DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING = 0.01f; + // 10 megacakes = .01 ARC + public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES = 10_000_000; /** @hide */ - public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX = 500; + public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT = 3; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX = 60; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES = arcToCake(60); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_INSTANT = 5; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_INSTANT_CAKES = + arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_ONGOING_CAKES = + arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_MAX = 500; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_SEEN_WITHIN_15_MAX_CAKES = + arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT = 5; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES = + arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES = + arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX = 500; + public static final long DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT = 10; + public static final long DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX = 500; + public static final long DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT = 10; + public static final long DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING = 0; + public static final long DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX = 500; + public static final long DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(500); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES = + arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES = + arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP = 3; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES = + arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES = + arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP = 1; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP = 5; + public static final long DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES = arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE = 5; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(5); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE = 4; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE = 4; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE = 3; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE = 3; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(3); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE = 2; + public static final long DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(2); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE = - 2; + public static final long + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(2); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE = 1; + public static final long DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES = + arcToCake(1); /** @hide */ - public static final int DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE = 10; + public static final long DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES = arcToCake(10); // Default values JobScheduler factors // TODO: add time_since_usage variable to min satiated balance factors /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 20000; + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES = arcToCake(15000); /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 10000; + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES = + arcToCake(7500); /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP = 2000; + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES = arcToCake(2000); /** @hide */ - public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000; + public static final long DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES = arcToCake(60000); /** @hide */ - public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 100_000; + public static final long DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(29_000); /** @hide */ - public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 460_000; + // TODO: set hard limit based on device type (phone vs tablet vs etc) + battery size + public static final long DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000); // TODO: add JobScheduler modifier default values /** @hide */ - public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0; + public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0); /** @hide */ - public static final float DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING = 0.5f; + public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES = CAKE_IN_ARC / 2; /** @hide */ - public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX = 15000; + public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES = arcToCake(15000); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT = 1; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX = 10; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT = 5; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES = + arcToCake(5); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES = + arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX = 5000; + public static final long DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES = arcToCake(5000); /** @hide */ - public static final int DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT = 10; + public static final long DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX = 5000; + public static final long DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES = arcToCake(5000); /** @hide */ - public static final int DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT = 10; + public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING = 0; + public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ - public static final int DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX = 5000; + public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(5000); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_START_CTP = 3; + public static final long DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP = 2; + public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP = 30; + public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES = arcToCake(30); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE = 10; + public static final long DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES = arcToCake(10); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE = 5; + public static final long DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES = arcToCake(5); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE = 8; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES = arcToCake(8); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE = 4; + public static final long DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE = 6; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES = arcToCake(6); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE = 3; + public static final long DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES = arcToCake(3); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE = 4; + public static final long DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES = arcToCake(4); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE = 2; + public static final long DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE = 2; + public static final long DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES = arcToCake(2); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE = 1; + public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1); /** @hide */ - public static final int DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE = 60; + public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index c2e81882eed2..d0f719b13b89 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -16,43 +16,43 @@ package com.android.server.tare; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE; -import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED; -import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE; @@ -97,12 +97,12 @@ import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE; -import static com.android.server.tare.TareUtils.arcToCake; import static com.android.server.tare.TareUtils.cakeToString; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; @@ -157,14 +157,15 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { AlarmManagerEconomicPolicy(InternalResourceService irs) { super(irs); mInternalResourceService = irs; - loadConstants(""); + loadConstants("", null); } @Override - void setup() { - super.setup(); + void setup(@NonNull DeviceConfig.Properties properties) { + super.setup(properties); ContentResolver resolver = mInternalResourceService.getContext().getContentResolver(); - loadConstants(Settings.Global.getString(resolver, TARE_ALARM_MANAGER_CONSTANTS)); + loadConstants(Settings.Global.getString(resolver, TARE_ALARM_MANAGER_CONSTANTS), + properties); } @Override @@ -178,6 +179,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { @Override long getMaxSatiatedBalance() { + // TODO(230501287): adjust balance based on whether the app has the SCHEDULE_EXACT_ALARM + // permission granted. Apps without the permission granted shouldn't need a high balance + // since they won't be able to use exact alarms. Apps with the permission granted could + // have a higher balance, or perhaps just those with the USE_EXACT_ALARM permission since + // that is limited to specific use cases. return mMaxSatiatedBalance; } @@ -209,7 +215,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { return mRewards.get(rewardId); } - private void loadConstants(String policyValuesString) { + private void loadConstants(String policyValuesString, + @Nullable DeviceConfig.Properties properties) { mActions.clear(); mRewards.clear(); @@ -219,145 +226,159 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceExempted = arcToCake(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, - DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED)); - mMinSatiatedBalanceOther = arcToCake(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, - DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP)); - mMaxSatiatedBalance = arcToCake(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE, - DEFAULT_AM_MAX_SATIATED_BALANCE)); - mInitialSatiatedConsumptionLimit = arcToCake(mParser.getInt( - KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT)); + mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES); + mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, + KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, + DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + mMaxSatiatedBalance = getConstantAsCake(mParser, properties, + KEY_AM_MAX_SATIATED_BALANCE, + DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES); + mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES); mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, - arcToCake(mParser.getInt( - KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT))); + getConstantAsCake(mParser, properties, + KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES)); - final long exactAllowWhileIdleWakeupBasePrice = arcToCake( - mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE)); + final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP)), + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES), exactAllowWhileIdleWakeupBasePrice)); mActions.put(ACTION_ALARM_WAKEUP_EXACT, new Action(ACTION_ALARM_WAKEUP_EXACT, - arcToCake(mParser.getInt(KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP)), - arcToCake(mParser.getInt(KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP, + DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES))); final long inexactAllowWhileIdleWakeupBasePrice = - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE)); + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP)), + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES), inexactAllowWhileIdleWakeupBasePrice)); mActions.put(ACTION_ALARM_WAKEUP_INEXACT, new Action(ACTION_ALARM_WAKEUP_INEXACT, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE)))); - - final long exactAllowWhileIdleNonWakeupBasePrice = - arcToCake(mParser.getInt( - KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE)); + DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES))); + final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP)), + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES), exactAllowWhileIdleNonWakeupBasePrice)); + mActions.put(ACTION_ALARM_NONWAKEUP_EXACT, new Action(ACTION_ALARM_NONWAKEUP_EXACT, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE)))); - - final long inexactAllowWhileIdleNonWakeupBasePrice = - arcToCake(mParser.getInt( - KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE)); - + DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES))); + + final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES); + final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(mParser, properties, + KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP, + DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES); mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE, - arcToCake(mParser.getInt( - KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP)), + inexactAllowWhileIdleNonWakeupCtp, inexactAllowWhileIdleNonWakeupBasePrice)); + mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT, new Action(ACTION_ALARM_NONWAKEUP_INEXACT, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP, - DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE)))); + DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES))); mActions.put(ACTION_ALARM_CLOCK, new Action(ACTION_ALARM_CLOCK, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP, - DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP)), - arcToCake(mParser.getInt( + DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE, - DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE)))); + DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES))); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, - arcToCake(mParser.getInt(KEY_AM_REWARD_TOP_ACTIVITY_INSTANT, - DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT)), - (long) (arcToCake(1) * mParser.getFloat(KEY_AM_REWARD_TOP_ACTIVITY_ONGOING, - DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING)), - arcToCake(mParser.getInt(KEY_AM_REWARD_TOP_ACTIVITY_MAX, - DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX)))); + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_TOP_ACTIVITY_INSTANT, + DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_TOP_ACTIVITY_ONGOING, + DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_TOP_ACTIVITY_MAX, + DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN, - arcToCake(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT, - DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT)), - arcToCake(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING, - DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING)), - arcToCake(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_MAX, - DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX)))); + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT, + DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING, + DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_NOTIFICATION_SEEN_MAX, + DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_INTERACTION, new Reward(REWARD_NOTIFICATION_INTERACTION, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT, - DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING, - DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX, - DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX)))); + DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION, - arcToCake(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT, - DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT)), - arcToCake(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING, - DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING)), - arcToCake(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_MAX, - DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX)))); + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT, + DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING, + DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_WIDGET_INTERACTION_MAX, + DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_OTHER_USER_INTERACTION, new Reward(REWARD_OTHER_USER_INTERACTION, - arcToCake(mParser.getInt(KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT, - DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, + KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT, + DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING, - DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX, - DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX)))); + DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES))); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java index 2109a8575777..c3eb5bf51161 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java @@ -18,6 +18,7 @@ package com.android.server.tare; import android.annotation.NonNull; import android.annotation.Nullable; +import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.SparseArray; @@ -57,10 +58,10 @@ public class CompleteEconomicPolicy extends EconomicPolicy { } @Override - void setup() { - super.setup(); + void setup(@NonNull DeviceConfig.Properties properties) { + super.setup(properties); for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { - mEnabledEconomicPolicies.valueAt(i).setup(); + mEnabledEconomicPolicies.valueAt(i).setup(properties); } updateMaxBalances(); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index 3a26aae217c2..d401373c0066 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -16,6 +16,8 @@ package com.android.server.tare; +import static android.app.tare.EconomyManager.parseCreditValue; + import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; @@ -27,7 +29,9 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; +import android.util.KeyValueListParser; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -170,7 +174,7 @@ public abstract class EconomicPolicy { } @CallSuper - void setup() { + void setup(@NonNull DeviceConfig.Properties properties) { for (int i = 0; i < NUM_COST_MODIFIERS; ++i) { final Modifier modifier = COST_MODIFIER_BY_INDEX[i]; if (modifier != null) { @@ -409,6 +413,22 @@ public abstract class EconomicPolicy { return "UNKNOWN_REWARD:" + Integer.toHexString(eventId); } + protected long getConstantAsCake(@NonNull KeyValueListParser parser, + @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) { + // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig + // config can cause issues since the scales may be different, so use one or the other. + if (parser.size() > 0) { + // User settings take precedence. Just stick with the Settings constants, even if there + // are invalid values. It's not worth the time to evaluate all the key/value pairs to + // make sure there are valid ones before deciding. + return parseCreditValue(parser.getString(key, null), defaultValCake); + } + if (properties != null) { + return parseCreditValue(properties.getString(key, null), defaultValCake); + } + return defaultValCake; + } + protected static void dumpActiveModifiers(IndentingPrintWriter pw) { for (int i = 0; i < NUM_COST_MODIFIERS; ++i) { pw.print("Modifier "); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index ce4604f80f50..2118eeb45d70 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -731,7 +731,7 @@ public class InternalResourceService extends SystemService { registerListeners(); mCurrentBatteryLevel = getCurrentBatteryLevel(); mHandler.post(this::setupHeavyWork); - mCompleteEconomicPolicy.setup(); + mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties()); } } @@ -1014,10 +1014,17 @@ public class InternalResourceService extends SystemService { Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS), false, this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS), false, this); - onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TARE)); + onPropertiesChanged(getAllDeviceConfigProperties()); updateEnabledStatus(); } + @NonNull + DeviceConfig.Properties getAllDeviceConfigProperties() { + // Don't want to cache the Properties object locally in case it ends up being large, + // especially since it'll only be used once/infrequently (during setup or on a change). + return DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TARE); + } + @Override public void onChange(boolean selfChange, Uri uri) { if (uri.equals(Settings.Global.getUriFor(Settings.Global.ENABLE_TARE))) { @@ -1030,6 +1037,7 @@ public class InternalResourceService extends SystemService { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { + boolean economicPolicyUpdated = false; synchronized (mLock) { for (String name : properties.getKeyset()) { if (name == null) { @@ -1039,6 +1047,12 @@ public class InternalResourceService extends SystemService { case KEY_DC_ENABLE_TARE: updateEnabledStatus(); break; + default: + if (!economicPolicyUpdated + && (name.startsWith("am") || name.startsWith("js"))) { + updateEconomicPolicy(); + economicPolicyUpdated = true; + } } } } @@ -1072,7 +1086,7 @@ public class InternalResourceService extends SystemService { mCompleteEconomicPolicy.tearDown(); mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this); if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) { - mCompleteEconomicPolicy.setup(); + mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties()); if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() || hardLimit != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 99b93ce5c22c..948f0a71510c 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -16,48 +16,48 @@ package com.android.server.tare; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE; -import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; -import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT; -import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE; -import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED; -import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX; -import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE; @@ -106,12 +106,12 @@ import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE; -import static com.android.server.tare.TareUtils.arcToCake; import static com.android.server.tare.TareUtils.cakeToString; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; @@ -159,14 +159,15 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { JobSchedulerEconomicPolicy(InternalResourceService irs) { super(irs); mInternalResourceService = irs; - loadConstants(""); + loadConstants("", null); } @Override - void setup() { - super.setup(); + void setup(@NonNull DeviceConfig.Properties properties) { + super.setup(properties); ContentResolver resolver = mInternalResourceService.getContext().getContentResolver(); - loadConstants(Settings.Global.getString(resolver, TARE_JOB_SCHEDULER_CONSTANTS)); + loadConstants(Settings.Global.getString(resolver, TARE_JOB_SCHEDULER_CONSTANTS), + properties); } @Override @@ -211,7 +212,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { return mRewards.get(rewardId); } - private void loadConstants(String policyValuesString) { + private void loadConstants(String policyValuesString, + @Nullable DeviceConfig.Properties properties) { mActions.clear(); mRewards.clear(); @@ -221,118 +223,153 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceExempted = arcToCake( - mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, - DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED)); - mMinSatiatedBalanceOther = arcToCake( - mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, - DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP)); - mMaxSatiatedBalance = arcToCake(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE, - DEFAULT_JS_MAX_SATIATED_BALANCE)); - mInitialSatiatedConsumptionLimit = arcToCake(mParser.getInt( - KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT)); + mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES); + mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, + KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, + DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + mMaxSatiatedBalance = getConstantAsCake(mParser, properties, + KEY_JS_MAX_SATIATED_BALANCE, + DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES); + mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_JS_INITIAL_CONSUMPTION_LIMIT, + DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES); mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, - arcToCake(mParser.getInt( - KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT))); + getConstantAsCake(mParser, properties, + KEY_JS_HARD_CONSUMPTION_LIMIT, + DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES)); mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP, - DEFAULT_JS_ACTION_JOB_MAX_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_START_CTP, + DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MAX_RUNNING, new Action(ACTION_JOB_MAX_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_HIGH_START, new Action(ACTION_JOB_HIGH_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_START_CTP, - DEFAULT_JS_ACTION_JOB_HIGH_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_START_CTP, + DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_HIGH_RUNNING, new Action(ACTION_JOB_HIGH_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_DEFAULT_START, new Action(ACTION_JOB_DEFAULT_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_START_CTP, - DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_START_CTP, + DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_DEFAULT_RUNNING, new Action(ACTION_JOB_DEFAULT_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_LOW_START, new Action(ACTION_JOB_LOW_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_START_CTP, - DEFAULT_JS_ACTION_JOB_LOW_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_START_CTP, + DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_LOW_RUNNING, new Action(ACTION_JOB_LOW_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MIN_START, new Action(ACTION_JOB_MIN_START, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_START_CTP, - DEFAULT_JS_ACTION_JOB_MIN_START_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_START_CTP, + DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MIN_RUNNING, new Action(ACTION_JOB_MIN_RUNNING, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_RUNNING_CTP, - DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_RUNNING_CTP, + DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_TIMEOUT, new Action(ACTION_JOB_TIMEOUT, - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP, - DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP)), - arcToCake(mParser.getInt(KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE, - DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE)))); + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP, + DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE, + DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES))); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, - arcToCake(mParser.getInt(KEY_JS_REWARD_TOP_ACTIVITY_INSTANT, - DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT)), - (long) (arcToCake(1) * mParser.getFloat(KEY_JS_REWARD_TOP_ACTIVITY_ONGOING, - DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING)), - arcToCake(mParser.getInt(KEY_JS_REWARD_TOP_ACTIVITY_MAX, - DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX)))); + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_TOP_ACTIVITY_INSTANT, + DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_TOP_ACTIVITY_ONGOING, + DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_TOP_ACTIVITY_MAX, + DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN, - arcToCake(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT, - DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT)), - arcToCake(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING, - DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING)), - arcToCake(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_MAX, - DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX)))); + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT, + DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING, + DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_NOTIFICATION_SEEN_MAX, + DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_INTERACTION, new Reward(REWARD_NOTIFICATION_INTERACTION, - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT, - DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING, - DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX, - DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX)))); + DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION, - arcToCake(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT, - DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT)), - arcToCake(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING, - DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING)), - arcToCake(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_MAX, - DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX)))); + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT, + DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING, + DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_WIDGET_INTERACTION_MAX, + DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_OTHER_USER_INTERACTION, new Reward(REWARD_OTHER_USER_INTERACTION, - arcToCake(mParser.getInt(KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT, - DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT)), - arcToCake(mParser.getInt( + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT, + DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING, - DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING)), - arcToCake(mParser.getInt( + DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES), + getConstantAsCake(mParser, properties, KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX, - DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX)))); + DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES))); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/tare/README.md b/apex/jobscheduler/service/java/com/android/server/tare/README.md index 72d506972dd1..e338ed1c6987 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/README.md +++ b/apex/jobscheduler/service/java/com/android/server/tare/README.md @@ -18,16 +18,17 @@ The key tenets of TARE are: In an ideal world, the system could be said to most efficiently allocate resources by maximizing its profits — by maximizing the aggregate sum of the difference between an action's price (that the app ends up paying) and the cost to produce by the system. This assumes that more important -actions have a higher price than less important actions. With this assumption, maximizing profits -implies that the system runs the most important work first and proceeds in decreasing order of -importance. Of course, that also means the system will not run anything where an app would pay less -for the action than the system's cost to produce that action. Some of this breaks down when we throw -TOP apps into the mix — TOP apps pay 0 for all actions, even though the CTP may be greater -than 0. This is to ensure ideal user experience for the app the user is actively interacting with. -Similar caveats exist for system-critical processes (such as the OS itself) and apps running -foreground services (since those could be critical to user experience, as is the case for media and -navigation apps). Excluding those caveats/special situations, maximizing profits of actions -performed by apps in the background should be the target. +actions have a higher price than less important actions and all actors have perfect information and +convey that information accurately. With these assumptions, maximizing profits implies that the +system runs the most important work first and proceeds in decreasing order of importance. Of course, +that also means the system will not run anything where an app would pay less for the action than the +system's cost to produce that action. Some of this breaks down when we throw TOP apps into the mix +— TOP apps pay 0 for all actions, even though the CTP may be greater than 0. This is to ensure +ideal user experience for the app the user is actively interacting with. Similar caveats exist for +system-critical processes (such as the OS itself) and apps running foreground services (since those +could be critical to user experience, as is the case for media and navigation apps). Excluding those +caveats/special situations, maximizing profits of actions performed by apps in the background should +be the target. To achieve the goal laid out by TARE, we use Android Resource Credits (ARCs for short) as the internal/representative currency of the system. @@ -101,11 +102,37 @@ Tare Improvement Proposal #1 (TIP1) separated allocation (to apps) from supply ( allowed apps to accrue credits as appropriate while still limiting the total number of credits consumed. +# Potential Future Changes + +These are some ideas for further changes. There's no guarantee that they'll be implemented. + +* Include additional components and policies for them. TARE may benefit from adding policies for + components such as broadcast dispatching, network traffic, location requests, and sensor usage. +* Have a separate "account" for critical/special actions. In other words, have two accounts for each + app, where one acts like a special savings account and is only allowed to be used for special + actions such as expedited job execution. The second account would have a lower maximum than the + main account, but would help to make sure that normal actions don't interfere too much with more + critical actions. +* Transferring credits from one app to another. For apps that rely on others for some pieces of + work, it may be beneficial to allow the requesting app to transfer, donate, or somehow make + available some of its own credits to the app doing the work in order to make sure the working app + has enough credits available to do the work. +* Formulate values based on device hardware. For example, adjust the consumption limit based on the + battery size, or the price and/or CTP of actions based on hardware efficiency. +* Price discovery via an auction system. Instead of just setting a fixed price that may be modified + by device and app states, let an app say how much it's willing to pay for a specific action and + then have a small auction when the system needs to decide which app to perform the action for + first or how much to charge the app. + # Definitions * ARC: Android Resource Credits are the "currency" units used as an abstraction layer over the real battery drain. They allow the system to standardize costs and prices across various devices. * Cake: A lie; also the smallest unit of an ARC (1 cake = one-billionth of an ARC = 1 nano-ARC). When the apps request to do something, we shall let them eat cake. -* NARC: The smallest unit of an ARC. A narc is 1 nano-ARC. +* Cost to produce (CTP): An economic term that refers to the total cost incurred by a business to + produce a specific quantity of a product or offer a service. In TARE's context, CTP is meant to be + the estimated cost t ohe system to accomplish a certain action. These "actions" are basically APIs + that apps use to get something done. So the idea is to define the base cost for an app to use a + specific API. * Satiated: used to refer to when the device is fully charged (at 100% battery level)
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java index 87db8637ddac..6b6984f6ac17 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java @@ -16,6 +16,8 @@ package com.android.server.tare; +import static android.app.tare.EconomyManager.CAKE_IN_ARC; + import android.annotation.NonNull; import android.annotation.SuppressLint; import android.util.IndentingPrintWriter; @@ -26,8 +28,6 @@ import java.text.SimpleDateFormat; import java.time.Clock; class TareUtils { - private static final long CAKE_IN_ARC = 1_000_000_000L; - @SuppressLint("SimpleDateFormat") private static final SimpleDateFormat sDumpDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -35,10 +35,6 @@ class TareUtils { @VisibleForTesting static Clock sSystemClock = Clock.systemUTC(); - static long arcToCake(int arcs) { - return arcs * CAKE_IN_ARC; - } - static void dumpTime(IndentingPrintWriter pw, long time) { pw.print(sDumpDateFormat.format(time)); } @@ -56,7 +52,7 @@ class TareUtils { if (cakes == 0) { return "0 ARCs"; } - final long sub = Math.abs(cakes) % CAKE_IN_ARC; + final long sub = cakes % CAKE_IN_ARC; final long arcs = cakeToArc(cakes); if (arcs == 0) { return sub == 1 @@ -65,11 +61,11 @@ class TareUtils { } StringBuilder sb = new StringBuilder(); sb.append(arcs); - if (sub > 0) { - sb.append(".").append(sub / (CAKE_IN_ARC / 1000)); + if (sub != 0) { + sb.append(".").append(String.format("%03d", Math.abs(sub) / (CAKE_IN_ARC / 1000))); } sb.append(" ARC"); - if (arcs != 1 || sub > 0) { + if (arcs != 1 || sub != 0) { sb.append("s"); } return sb.toString(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index e9c29b8aa0a5..c802d20a5a57 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -261,8 +261,10 @@ public class KeyguardManager { CharSequence title, CharSequence description, int userId, boolean disallowBiometricsIfPolicyExists) { Intent intent = this.createConfirmDeviceCredentialIntent(title, description, userId); - intent.putExtra(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, - disallowBiometricsIfPolicyExists); + if (intent != null) { + intent.putExtra(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, + disallowBiometricsIfPolicyExists); + } return intent; } diff --git a/core/java/android/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/service/games/GameSessionTrampolineActivity.java b/core/java/android/service/games/GameSessionTrampolineActivity.java index e890876df3e7..b23791842284 100644 --- a/core/java/android/service/games/GameSessionTrampolineActivity.java +++ b/core/java/android/service/games/GameSessionTrampolineActivity.java @@ -105,6 +105,7 @@ public final class GameSessionTrampolineActivity extends Activity { FUTURE_KEY); future.completeExceptionally(e); finish(); + overridePendingTransition(0, 0); } } @@ -125,5 +126,6 @@ public final class GameSessionTrampolineActivity extends Activity { FUTURE_KEY); future.complete(new GameSessionActivityResult(resultCode, data)); finish(); + overridePendingTransition(0, 0); } } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index e6cf68367ae6..583252756b92 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -47,6 +47,7 @@ public class InsetsSource implements Parcelable { private final Rect mFrame; private @Nullable Rect mVisibleFrame; private boolean mVisible; + private boolean mInsetsRoundedCornerFrame; private final Rect mTmpFrame = new Rect(); @@ -63,6 +64,7 @@ public class InsetsSource implements Parcelable { mVisibleFrame = other.mVisibleFrame != null ? new Rect(other.mVisibleFrame) : null; + mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame; } public void set(InsetsSource other) { @@ -71,6 +73,7 @@ public class InsetsSource implements Parcelable { mVisibleFrame = other.mVisibleFrame != null ? new Rect(other.mVisibleFrame) : null; + mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame; } public void setFrame(int left, int top, int right, int bottom) { @@ -110,6 +113,14 @@ public class InsetsSource implements Parcelable { return mVisibleFrame == null || !mVisibleFrame.isEmpty(); } + public boolean getInsetsRoundedCornerFrame() { + return mInsetsRoundedCornerFrame; + } + + public void setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) { + mInsetsRoundedCornerFrame = insetsRoundedCornerFrame; + } + /** * Calculates the insets this source will cause to a client window. * @@ -225,6 +236,7 @@ public class InsetsSource implements Parcelable { pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString()); } pw.print(" visible="); pw.print(mVisible); + pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame); pw.println(); } @@ -247,6 +259,7 @@ public class InsetsSource implements Parcelable { if (mVisible != that.mVisible) return false; if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; + if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false; return mFrame.equals(that.mFrame); } @@ -256,6 +269,7 @@ public class InsetsSource implements Parcelable { result = 31 * result + mFrame.hashCode(); result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0); result = 31 * result + (mVisible ? 1 : 0); + result = 31 * result + (mInsetsRoundedCornerFrame ? 1 : 0); return result; } @@ -268,6 +282,7 @@ public class InsetsSource implements Parcelable { mVisibleFrame = null; } mVisible = in.readBoolean(); + mInsetsRoundedCornerFrame = in.readBoolean(); } @Override @@ -286,6 +301,7 @@ public class InsetsSource implements Parcelable { dest.writeInt(0); } dest.writeBoolean(mVisible); + dest.writeBoolean(mInsetsRoundedCornerFrame); } @Override @@ -294,6 +310,7 @@ public class InsetsSource implements Parcelable { + "mType=" + InsetsState.typeToString(mType) + ", mFrame=" + mFrame.toShortString() + ", mVisible=" + mVisible + + ", mInsetsRoundedCornerFrame=" + mInsetsRoundedCornerFrame + "}"; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index eb746080de15..9d6b982c3571 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -294,9 +294,16 @@ public class InsetsState implements Parcelable { return RoundedCorners.NO_ROUNDED_CORNERS; } // If mRoundedCornerFrame is set, we should calculate the new RoundedCorners based on this - // frame. It's used for split-screen mode and devices with a task bar. - if (!mRoundedCornerFrame.isEmpty() && !mRoundedCornerFrame.equals(mDisplayFrame)) { - return mRoundedCorners.insetWithFrame(frame, mRoundedCornerFrame); + // frame. + final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame); + for (InsetsSource source : mSources) { + if (source != null && source.getInsetsRoundedCornerFrame()) { + final Insets insets = source.calculateInsets(roundedCornerFrame, false); + roundedCornerFrame.inset(insets); + } + } + if (!roundedCornerFrame.isEmpty() && !roundedCornerFrame.equals(mDisplayFrame)) { + return mRoundedCorners.insetWithFrame(frame, roundedCornerFrame); } if (mDisplayFrame.equals(frame)) { return mRoundedCorners; diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 314b0a0c81db..a06ba9be4689 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -247,6 +247,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O // In order to make the list view work with CollapsingToolbarLayout, // we have to enable the nested scrolling feature of the list view. getListView().setNestedScrollingEnabled(true); + getListView().setDivider(null); } @Override diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 18fde4794969..5fe111148c91 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -27,7 +27,6 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; -import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.R; @@ -222,6 +221,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { convertView = mInflater.inflate( R.layout.app_language_picker_current_locale_item, parent, false); title = convertView.findViewById(R.id.language_picker_item); + addStateDescriptionIntoCurrentLocaleItem(convertView); } else { convertView = mInflater.inflate( R.layout.language_picker_item, parent, false); @@ -234,6 +234,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { if (!(convertView instanceof ViewGroup)) { convertView = mInflater.inflate( R.layout.app_language_picker_current_locale_item, parent, false); + addStateDescriptionIntoCurrentLocaleItem(convertView); } updateTextView( convertView, convertView.findViewById(R.id.language_picker_item), position); @@ -369,4 +370,9 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { : View.TEXT_DIRECTION_LTR); } } + + private void addStateDescriptionIntoCurrentLocaleItem(View root) { + String description = root.getContext().getResources().getString(R.string.checked); + root.setStateDescription(description); + } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 6424989c6b4f..1b52aa93a51d 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -28,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; @@ -189,7 +190,8 @@ public class InteractionJankMonitor { public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; public static final int CUJ_SPLIT_SCREEN_ENTER = 49; public static final int CUJ_SPLIT_SCREEN_EXIT = 50; - public static final int CUJ_SPLIT_SCREEN_RESIZE = 51; + public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. + public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; private static final int NO_STATSD_LOGGING = -1; @@ -249,6 +251,7 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE, }; @@ -321,6 +324,7 @@ public class InteractionJankMonitor { CUJ_SUW_LOADING_SCREEN_FOR_STATUS, CUJ_SPLIT_SCREEN_ENTER, CUJ_SPLIT_SCREEN_EXIT, + CUJ_LOCKSCREEN_LAUNCH_CAMERA, CUJ_SPLIT_SCREEN_RESIZE }) @Retention(RetentionPolicy.SOURCE) @@ -742,6 +746,8 @@ public class InteractionJankMonitor { return "SPLIT_SCREEN_ENTER"; case CUJ_SPLIT_SCREEN_EXIT: return "SPLIT_SCREEN_EXIT"; + case CUJ_LOCKSCREEN_LAUNCH_CAMERA: + return "CUJ_LOCKSCREEN_LAUNCH_CAMERA"; case CUJ_SPLIT_SCREEN_RESIZE: return "CUJ_SPLIT_SCREEN_RESIZE"; } diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index 94a8ae5a8a67..f2c27a494fc9 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -76,6 +76,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa ContentResolver contentResolver = context.getContentResolver(); mPostScrollDelayMillis = Settings.Global.getLong(contentResolver, SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT); + Log.d(TAG, "screenshot.scroll_capture_delay = " + mPostScrollDelayMillis); } /** Based on ViewRootImpl#updateColorModeIfNeeded */ @@ -271,6 +272,13 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa Rect viewCaptureArea = new Rect(scrollResult.availableArea); viewCaptureArea.offset(0, -scrollResult.scrollDelta); + view.postOnAnimationDelayed( + () -> doCapture(scrollResult, view, viewCaptureArea, onComplete), + mPostScrollDelayMillis); + } + + private void doCapture(ScrollResult scrollResult, V view, Rect viewCaptureArea, + Consumer<Rect> onComplete) { int result = mRenderer.renderView(view, viewCaptureArea); if (result == HardwareRenderer.SYNC_OK || result == HardwareRenderer.SYNC_REDRAW_REQUESTED) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4075c5f4d8ae..217166c6810b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -377,6 +377,8 @@ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" /> <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" /> <protected-broadcast android:name="com.android.internal.action.EUICC_FACTORY_RESET" /> + <protected-broadcast + android:name="com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS" /> <protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" /> <protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" /> <protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" /> @@ -6790,7 +6792,7 @@ android:excludeFromRecents="true" android:exported="true" android:permission="android.permission.MANAGE_GAME_ACTIVITY" - android:theme="@style/Theme.Translucent.NoTitleBar"> + android:theme="@style/Theme.GameSessionTrampoline"> </activity> <receiver android:name="com.android.server.BootReceiver" diff --git a/core/res/res/layout/app_language_picker_current_locale_item.xml b/core/res/res/layout/app_language_picker_current_locale_item.xml index bf6d9639791a..990e26c8f6be 100644 --- a/core/res/res/layout/app_language_picker_current_locale_item.xml +++ b/core/res/res/layout/app_language_picker_current_locale_item.xml @@ -39,6 +39,6 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_check_24dp" - app:tint="#0F9D58"/> + app:tint="?attr/colorAccentPrimaryVariant"/> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/app_language_picker_system_current.xml b/core/res/res/layout/app_language_picker_system_current.xml index 341ee2528671..300da25ea445 100644 --- a/core/res/res/layout/app_language_picker_system_current.xml +++ b/core/res/res/layout/app_language_picker_system_current.xml @@ -40,6 +40,6 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_check_24dp" - app:tint="#0F9D58"/> + app:tint="?attr/colorAccentPrimaryVariant"/> </LinearLayout> </LinearLayout> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index bf42da080390..a60862b74e15 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -894,6 +894,22 @@ please see themes_device_defaults.xml. <!-- @hide Special theme for the default system Activity-based Alert dialogs. --> <style name="Theme.Dialog.Confirmation" parent="Theme.DeviceDefault.Dialog.Alert.DayNight" /> + <!-- @hide Theme for GameSessionTrampolineActivity that prevents showing UI and activity + transitions. --> + <style name="Theme.GameSessionTrampoline"> + <item name="backgroundDimEnabled">false</item> + <item name="colorBackgroundCacheHint">@null</item> + <item name="navigationBarColor">@color/transparent</item> + <item name="statusBarColor">@color/transparent</item> + <item name="windowAnimationStyle">@null</item> + <item name="windowBackground">@null</item> + <item name="windowContentOverlay">@null</item> + <item name="windowDrawsSystemBarBackgrounds">true</item> + <item name="windowIsFloating">true</item> + <item name="windowIsTranslucent">true</item> + <item name="windowNoTitle">true</item> + </style> + <!-- Theme for a window that looks like a toast. --> <style name="Theme.Toast" parent="Theme.DeviceDefault.Dialog"> <item name="windowBackground">?attr/toastFrameBackground</item> diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index e083b0d460a2..3733bfa586d1 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -48,6 +48,7 @@ public class UriTest extends TestCase { public void testParcelling() { parcelAndUnparcel(Uri.parse("foo:bob%20lee")); parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(Uri.fromParts("https", "www.google.com", null)); parcelAndUnparcel(new Uri.Builder() .scheme("http") .authority("crazybob.org") @@ -890,9 +891,62 @@ public class UriTest extends TestCase { Throwable targetException = expected.getTargetException(); // Check that the exception was thrown for the correct reason. assertEquals("Unknown representation: 0", targetException.getMessage()); + } finally { + parcel.recycle(); } } + private Uri buildUriFromRawParcel(boolean argumentsEncoded, + String scheme, + String authority, + String path, + String query, + String fragment) { + // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). + final int representation = argumentsEncoded ? 1 : 2; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeInt(3); // hierarchical + parcel.writeString8(scheme); + parcel.writeInt(representation); + parcel.writeString8(authority); + parcel.writeInt(representation); + parcel.writeString8(path); + parcel.writeInt(representation); + parcel.writeString8(query); + parcel.writeInt(representation); + parcel.writeString8(fragment); + parcel.setDataPosition(0); + return Uri.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + + public void testUnparcelMalformedPath() { + // Regression tests for b/171966843. + + // Test cases with arguments encoded (covering testing `scheme` * `authority` options). + Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/@evil.com", uri0.toString()); + Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); + Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + assertEquals("http::/@evil.com", uri2.toString()); + Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + assertEquals("@evil.com", uri3.toString()); + + // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). + Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/%40evil.com", uriA.toString()); + Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + assertEquals("//google.com/%40evil.com", uriB.toString()); + Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + assertEquals("http::/%40evil.com", uriC.toString()); + Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index e50b9a1cd469..81caf7786cf5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -16,7 +16,6 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; @@ -49,7 +48,8 @@ import java.util.concurrent.Executor; class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ - private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); + @VisibleForTesting + final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); /** * Mapping from the client assigned unique token to the TaskFragment parent @@ -120,25 +120,29 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment * @param activityIntent Intent to start the secondary Activity with. * @param activityOptions ActivityOptions to start the secondary Activity with. + * @param windowingMode the windowing mode to set for the TaskFragments. */ void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, - @Nullable Bundle activityOptions, @NonNull SplitRule rule) { + @Nullable Bundle activityOptions, @NonNull SplitRule rule, + @WindowingMode int windowingMode) { final IBinder ownerToken = launchingActivity.getActivityToken(); // Create or resize the launching TaskFragment. if (mFragmentInfos.containsKey(launchingFragmentToken)) { resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds); + wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(), + windowingMode); } else { createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, - launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity); + launchingFragmentBounds, windowingMode, launchingActivity); } // Create a TaskFragment for the secondary activity. createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken, - secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent, + secondaryFragmentBounds, windowingMode, activityIntent, activityOptions); // Set adjacent to each other so that the containers below will be invisible. @@ -153,6 +157,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { resizeTaskFragment(wct, fragmentToken, new Rect()); setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); + setWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); } /** @@ -255,6 +260,15 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds); } + private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken, + @WindowingMode int windowingMode) { + if (!mFragmentInfos.containsKey(fragmentToken)) { + throw new IllegalArgumentException( + "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); + } + wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode); + } + void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { if (!mFragmentInfos.containsKey(fragmentToken)) { throw new IllegalArgumentException( 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 2328f76a7130..b370e59ac7c8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -257,9 +257,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } - final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration()); + final boolean wasInPip = taskContainer.isInPictureInPicture(); final boolean isInPIp = isInPictureInPicture(config); - taskContainer.setConfiguration(config); + taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode()); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; @@ -278,8 +278,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * bounds is large enough for at least one split rule. */ private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { - if (!taskContainer.isTaskBoundsInitialized()) { - // We don't know about the Task bounds yet. + if (!taskContainer.isTaskBoundsInitialized() + || !taskContainer.isWindowingModeInitialized()) { + // We don't know about the Task bounds/windowingMode yet. return; } @@ -293,7 +294,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private boolean supportSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. - if (isInPictureInPicture(taskContainer.getConfiguration())) { + if (taskContainer.isInPictureInPicture()) { return false; } // Check if the parent container bounds can support any split rule. @@ -461,8 +462,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } - updateAnimationOverride(taskContainer); } + if (!taskContainer.isWindowingModeInitialized()) { + taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration() + .windowConfiguration.getWindowingMode()); + } + updateAnimationOverride(taskContainer); return container; } 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 ee5a322eed4f..e64e5d1c66d5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -16,10 +16,11 @@ package androidx.window.extensions.embedding; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration; +import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.Intent; import android.graphics.Rect; @@ -111,13 +112,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment + final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( - null /* activity */, primaryActivity, primaryContainer.getTaskId()); + null /* activity */, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); + final int windowingMode = mController.getTaskContainer(taskId) + .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, - WINDOWING_MODE_MULTI_WINDOW); + windowingMode); secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. @@ -173,7 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), - launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW); + launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); applyTransaction(wct); return newContainer; @@ -189,15 +193,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) { TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); + final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); + final int windowingMode = mController.getTaskContainer(taskId) + .getWindowingModeForSplitTaskFragment(bounds); if (container == null || container == containerToAvoid) { - container = mController.newContainer(activity, activity.getTaskId()); - + container = mController.newContainer(activity, taskId); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( container.getTaskFragmentToken(), activity.getActivityToken(), bounds, - WINDOWING_MODE_MULTI_WINDOW); + windowingMode); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), @@ -206,6 +212,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); + updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } return container; @@ -237,14 +244,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity.getTaskId()); } + final int taskId = primaryContainer.getTaskId(); TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */, - launchingActivity, primaryContainer.getTaskId()); + launchingActivity, taskId); + final int windowingMode = mController.getTaskContainer(taskId) + .getWindowingModeForSplitTaskFragment(primaryRectBounds); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, - activityIntent, activityOptions, rule); + activityIntent, activityOptions, rule, windowingMode); if (isPlaceholder) { // When placeholder is launched in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -292,6 +302,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } + final TaskContainer taskContainer = mController.getTaskContainer( + updatedContainer.getTaskId()); + final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( + primaryRectBounds); + updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); + updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @@ -323,6 +339,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds); } + private void updateTaskFragmentWindowingModeIfRegistered( + @NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container, + @WindowingMode int windowingMode) { + if (container.getInfo() != null) { + wct.setWindowingMode(container.getInfo().getToken(), windowingMode); + } + } + @Override void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index be793018d969..3c0762d81494 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -16,9 +16,14 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.res.Configuration; +import android.app.WindowConfiguration; +import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; @@ -37,9 +42,9 @@ class TaskContainer { /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); - /** Configuration of the Task. */ - @Nullable - private Configuration mConfiguration; + /** Windowing mode of this Task. */ + @WindowingMode + private int mWindowingMode = WINDOWING_MODE_UNDEFINED; /** Active TaskFragments in this Task. */ final List<TaskFragmentContainer> mContainers = new ArrayList<>(); @@ -81,13 +86,42 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } - @Nullable - Configuration getConfiguration() { - return mConfiguration; + void setWindowingMode(int windowingMode) { + mWindowingMode = windowingMode; + } + + /** Whether the Task windowing mode has been initialized. */ + boolean isWindowingModeInitialized() { + return mWindowingMode != WINDOWING_MODE_UNDEFINED; + } + + /** + * Returns the windowing mode for the TaskFragments below this Task, which should be split with + * other TaskFragments. + * + * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when + * the pair of TaskFragments are stacked due to the limited space. + */ + @WindowingMode + int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) { + // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it + // will be set to UNDEFINED which will then inherit the Task windowing mode. + if (taskFragmentBounds == null || taskFragmentBounds.isEmpty()) { + return WINDOWING_MODE_UNDEFINED; + } + // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. + // However, when the Task is in other multi windowing mode, such as Freeform, we need to + // have the activity windowing mode to match the Task, otherwise things like + // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the + // Task windowing mode if the Task is in multi window. + // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. + return WindowConfiguration.inMultiWindowMode(mWindowingMode) + ? mWindowingMode + : WINDOWING_MODE_MULTI_WINDOW; } - void setConfiguration(@Nullable Configuration configuration) { - mConfiguration = configuration; + boolean isInPictureInPicture() { + return mWindowingMode == WINDOWING_MODE_PINNED; } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index b06ce4c19d5c..1f12c4484159 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -16,15 +16,23 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import android.content.res.Configuration; +import android.graphics.Point; import android.platform.test.annotations.Presubmit; +import android.window.TaskFragmentInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -35,6 +43,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; + /** * Test class for {@link JetpackTaskFragmentOrganizer}. * @@ -48,6 +58,8 @@ public class JetpackTaskFragmentOrganizerTest { private static final int TASK_ID = 10; @Mock + private WindowContainerTransaction mTransaction; + @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; private JetpackTaskFragmentOrganizer mOrganizer; @@ -91,4 +103,24 @@ public class JetpackTaskFragmentOrganizerTest { verify(mOrganizer).unregisterRemoteAnimations(TASK_ID); } + + @Test + public void testExpandTaskFragment() { + final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID); + final TaskFragmentInfo info = createMockInfo(container); + mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); + container.setInfo(info); + + mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken()); + + verify(mTransaction).setWindowingMode(container.getInfo().getToken(), + WINDOWING_MODE_UNDEFINED); + } + + private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { + return new TaskFragmentInfo(container.getTaskFragmentToken(), + mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, + false /* isVisible */, new ArrayList<>(), new Point(), + false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 9fb08dffbab8..c7feb7e59de3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -16,6 +16,13 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -64,6 +71,56 @@ public class TaskContainerTest { } @Test + public void testIsWindowingModeInitialized() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + + assertFalse(taskContainer.isWindowingModeInitialized()); + + taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertTrue(taskContainer.isWindowingModeInitialized()); + } + + @Test + public void testGetWindowingModeForSplitTaskFragment() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final Rect splitBounds = new Rect(0, 0, 500, 1000); + + assertEquals(WINDOWING_MODE_MULTI_WINDOW, + taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + + taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertEquals(WINDOWING_MODE_MULTI_WINDOW, + taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + + taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM); + + assertEquals(WINDOWING_MODE_FREEFORM, + taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + + // Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then + // inherit the Task windowing mode + assertEquals(WINDOWING_MODE_UNDEFINED, + taskContainer.getWindowingModeForSplitTaskFragment(new Rect())); + } + + @Test + public void testIsInPictureInPicture() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + + assertFalse(taskContainer.isInPictureInPicture()); + + taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertFalse(taskContainer.isInPictureInPicture()); + + taskContainer.setWindowingMode(WINDOWING_MODE_PINNED); + + assertTrue(taskContainer.isInPictureInPicture()); + } + + @Test public void testIsEmpty() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); diff --git a/libs/WindowManager/Shell/res/layout/split_decor.xml b/libs/WindowManager/Shell/res/layout/split_decor.xml index dfb90affe7f6..443ecb2ed3f3 100644 --- a/libs/WindowManager/Shell/res/layout/split_decor.xml +++ b/libs/WindowManager/Shell/res/layout/split_decor.xml @@ -20,8 +20,8 @@ android:layout_width="match_parent"> <ImageView android:id="@+id/split_resizing_icon" - android:layout_height="@*android:dimen/starting_surface_icon_size" - android:layout_width="@*android:dimen/starting_surface_icon_size" + android:layout_height="@dimen/split_icon_size" + android:layout_width="@dimen/split_icon_size" android:layout_gravity="center" android:scaleType="fitCenter" android:padding="0dp" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index c21381d1486a..1dac9caba01e 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -87,6 +87,8 @@ <!-- How high we lift the divider when touching --> <dimen name="docked_stack_divider_lift_elevation">4dp</dimen> + <!-- Icon size for split screen --> + <dimen name="split_icon_size">72dp</dimen> <!-- Divider handle size for legacy split screen --> <dimen name="docked_divider_handle_width">16dp</dimen> <dimen name="docked_divider_handle_height">2dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 5dc6bd19853a..de30dbbe7e46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -73,6 +73,8 @@ public class SplitDecorManager extends WindowlessWindowManager { private Rect mBounds = new Rect(); private ValueAnimator mFadeAnimator; + private int mIconSize; + public SplitDecorManager(Configuration configuration, IconProvider iconProvider, SurfaceSession surfaceSession) { super(configuration, null /* rootSurface */, null /* hostInputToken */); @@ -104,6 +106,7 @@ public class SplitDecorManager extends WindowlessWindowManager { mHostLeash = rootLeash; mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this); + mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size); final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context) .inflate(R.layout.split_decor, null); mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon); @@ -171,14 +174,14 @@ public class SplitDecorManager extends WindowlessWindowManager { WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); - lp.width = mIcon.getIntrinsicWidth(); - lp.height = mIcon.getIntrinsicHeight(); + lp.width = mIconSize; + lp.height = mIconSize; mViewHost.relayout(lp); t.setLayer(mIconLeash, Integer.MAX_VALUE); } t.setPosition(mIconLeash, - newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2, - newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2); + newBounds.width() / 2 - mIconSize / 2, + newBounds.height() / 2 - mIconSize / 2); boolean show = newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height(); if (show != mShown) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index 0cea36ed48c8..28f59b53b5b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -111,8 +111,7 @@ public class DropZoneView extends FrameLayout { mColorDrawable = new ColorDrawable(); setBackgroundDrawable(mColorDrawable); - final int iconSize = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.starting_surface_icon_size); + final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size); mSplashScreenView = new ImageView(context); mSplashScreenView.setScaleType(ImageView.ScaleType.FIT_CENTER); addView(mSplashScreenView, diff --git a/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/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/res/drawable/keyguard_framed_avatar_background.xml b/packages/SystemUI/res/drawable/keyguard_framed_avatar_background.xml new file mode 100644 index 000000000000..a461bf836d61 --- /dev/null +++ b/packages/SystemUI/res/drawable/keyguard_framed_avatar_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/kg_framed_avatar_size"/> + <solid android:color="@color/kg_user_avatar_frame"/> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml index 9cf09ff328c4..6f3362308484 100644 --- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml +++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml @@ -22,18 +22,25 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="end"> - <com.android.systemui.statusbar.phone.UserAvatarView - android:id="@+id/kg_multi_user_avatar" - android:layout_width="@dimen/kg_framed_avatar_size" - android:layout_height="@dimen/kg_framed_avatar_size" - android:layout_centerHorizontal="true" + <!-- We add a background behind the UserAvatarView with the same color and with a circular shape + so that this view can be expanded into a Dialog or an Activity. --> + <FrameLayout + android:id="@+id/kg_multi_user_avatar_with_background" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="top|end" android:layout_marginEnd="16dp" - systemui:avatarPadding="0dp" - systemui:badgeDiameter="18dp" - systemui:badgeMargin="1dp" - systemui:frameColor="@color/kg_user_avatar_frame" - systemui:framePadding="0dp" - systemui:frameWidth="0dp"> - </com.android.systemui.statusbar.phone.UserAvatarView> + android:background="@drawable/keyguard_framed_avatar_background"> + <com.android.systemui.statusbar.phone.UserAvatarView + android:id="@+id/kg_multi_user_avatar" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" + systemui:avatarPadding="0dp" + systemui:badgeDiameter="18dp" + systemui:badgeMargin="1dp" + systemui:frameColor="@color/kg_user_avatar_frame" + systemui:framePadding="0dp" + systemui:frameWidth="0dp"> + </com.android.systemui.statusbar.phone.UserAvatarView> + </FrameLayout> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 21e56976a074..008299bd9b1c 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -60,6 +60,8 @@ <dimen name="global_actions_grid_item_layout_height">80dp</dimen> + <dimen name="qs_brightness_margin_bottom">16dp</dimen> + <!-- For large screens the security footer appears below the footer, same as phones in portrait --> <dimen name="qs_security_footer_single_line_height">48dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f92d6238e863..e1fa5752e2d3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -213,6 +213,8 @@ <!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] --> <string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or your organization</string> + <!-- Notification text displayed when screenshots are blocked by an IT admin. [CHAR LIMIT=100] --> + <string name="screenshot_blocked_by_admin">Taking screenshots is blocked by your IT admin</string> <!-- Label for UI element which allows editing the screenshot [CHAR LIMIT=30] --> <string name="screenshot_edit_label">Edit</string> <!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] --> @@ -1994,7 +1996,8 @@ app for debugging. Will not be seen by users. [CHAR LIMIT=20] --> <string name="heap_dump_tile_name">Dump SysUI Heap</string> - <!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]--> + <!-- Title for the privacy indicators dialog, only appears as part of a11y descriptions [CHAR LIMIT=NONE] --> + <string name="ongoing_privacy_dialog_a11y_title">In use</string> <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]--> <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string> diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index eb6705a2e979..f925eaa0e40b 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -15,8 +15,6 @@ */ package com.android.keyguard; -import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset; - import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; @@ -154,7 +152,7 @@ class NumPadAnimator { ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); TypedArray a = ctw.obtainStyledAttributes(customAttrs); - mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0, + mNormalColor = Utils.getPrivateAttrColorIfUnset(ctw, a, 0, 0, com.android.internal.R.attr.colorSurface); mHighlightColor = a.getColor(1, 0); a.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java b/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java index 35a70a5ed52b..0d1dc9d6a5dd 100644 --- a/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java +++ b/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java @@ -16,6 +16,8 @@ package com.android.systemui; +import android.annotation.StyleRes; +import android.content.res.TypedArray; import android.util.TypedValue; import android.view.View; import android.widget.TextView; @@ -23,9 +25,9 @@ import android.widget.TextView; /** * Utility class to update the font size when the configuration has changed. */ -public class FontSizeUtils { +public final class FontSizeUtils { - public static final float LARGE_TEXT_SCALE = 1.3f; + private FontSizeUtils() {} public static void updateFontSize(View parent, int viewId, int dimensId) { updateFontSize((TextView) parent.findViewById(viewId), dimensId); @@ -37,4 +39,20 @@ public class FontSizeUtils { v.getResources().getDimensionPixelSize(dimensId)); } } + + /** + * Updates the font size according to the style given. + * + * @param v Text to update. + * @param resId Style applying to the text. + */ + public static void updateFontSizeFromStyle(TextView v, @StyleRes int resId) { + int[] attrs = {android.R.attr.textSize}; + int indexOfAttrTextSize = 0; + TypedArray ta = v.getContext().obtainStyledAttributes(resId, attrs); + int updatedTextPixelSize = ta.getDimensionPixelSize(indexOfAttrTextSize, + (int) v.getTextSize()); + v.setTextSize(TypedValue.COMPLEX_UNIT_PX, updatedTextPixelSize); + ta.recycle(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 3641e1d52144..0df2730a48eb 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -380,7 +380,7 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec ) { if (hasTopRoundedCorner == hasTop && hasBottomRoundedCorner == hasBottom && - roundedCornerBottomSize == bottomSize && + roundedCornerTopSize == topSize && roundedCornerBottomSize == bottomSize) { return } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index b98fc03e3acd..8d6509874776 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -348,7 +348,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @Override public void onDisplayChanged(int displayId) { final int newRotation = mContext.getDisplay().getRotation(); - if (mOverlays != null && mRotation != newRotation) { + if ((mOverlays != null || mScreenDecorHwcWindow != null) + && mRotation != newRotation) { // We cannot immediately update the orientation. Otherwise // WindowManager is still deferring layout until it has finished dispatching // the config changes, which may cause divergence between what we draw @@ -362,11 +363,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab + mRotation); } - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] != null) { - final ViewGroup overlayView = mOverlays[i].getRootView(); - overlayView.getViewTreeObserver().addOnPreDrawListener( - new RestartingPreDrawListener(overlayView, i, newRotation)); + if (mOverlays != null) { + for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { + if (mOverlays[i] != null) { + final ViewGroup overlayView = mOverlays[i].getRootView(); + overlayView.getViewTreeObserver().addOnPreDrawListener( + new RestartingPreDrawListener(overlayView, i, newRotation)); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt index 9dbeb77ebc00..e316722b64ea 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt @@ -105,7 +105,7 @@ private fun Int.toLayoutGravity(@Surface.Rotation rotation: Int): Int = when (ro DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.BOTTOM DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.LEFT DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.TOP - else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT + else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.RIGHT } Surface.ROTATION_270 -> when (this) { DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.TOP diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index 70052fd45abf..5a214d1cd5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -21,41 +21,24 @@ import android.animation.ValueAnimator.AnimatorUpdateListener import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList -import android.graphics.drawable.GradientDrawable import com.android.internal.R import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.monet.ColorScheme -import com.android.systemui.util.getColorWithAlpha /** - * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme] - * is triggered. - */ -interface ColorTransition { - fun updateColorScheme(scheme: ColorScheme?) -} - -/** A generic implementation of [ColorTransition] so that we can define a factory method. */ -open class GenericColorTransition( - private val applyTheme: (ColorScheme?) -> Unit -) : ColorTransition { - override fun updateColorScheme(scheme: ColorScheme?) = applyTheme(scheme) -} - -/** - * A [ColorTransition] that animates between two specific colors. + * ColorTransition is responsible for managing the animation between two specific colors. * It uses a ValueAnimator to execute the animation and interpolate between the source color and * the target color. * * Selection of the target color from the scheme, and application of the interpolated color * are delegated to callbacks. */ -open class AnimatingColorTransition( +open class ColorTransition( private val defaultColor: Int, private val extractColor: (ColorScheme) -> Int, private val applyColor: (Int) -> Unit -) : AnimatorUpdateListener, ColorTransition { +) : AnimatorUpdateListener { private val argbEvaluator = ArgbEvaluator() private val valueAnimator = buildAnimator() @@ -70,7 +53,7 @@ open class AnimatingColorTransition( applyColor(currentColor) } - override fun updateColorScheme(scheme: ColorScheme?) { + fun updateColorScheme(scheme: ColorScheme?) { val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme) if (newTargetColor != targetColor) { sourceColor = currentColor @@ -93,9 +76,7 @@ open class AnimatingColorTransition( } } -typealias AnimatingColorTransitionFactory = - (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition -typealias GenericColorTransitionFactory = ((ColorScheme?) -> Unit) -> GenericColorTransition +typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> ColorTransition /** * ColorSchemeTransition constructs a ColorTransition for each color in the scheme @@ -105,26 +86,27 @@ typealias GenericColorTransitionFactory = ((ColorScheme?) -> Unit) -> GenericCol class ColorSchemeTransition internal constructor( private val context: Context, mediaViewHolder: MediaViewHolder, - animatingColorTransitionFactory: AnimatingColorTransitionFactory, - genericColorTransitionFactory: GenericColorTransitionFactory + colorTransitionFactory: ColorTransitionFactory ) { constructor(context: Context, mediaViewHolder: MediaViewHolder) : - this(context, mediaViewHolder, ::AnimatingColorTransition, ::GenericColorTransition) + this(context, mediaViewHolder, ::ColorTransition) val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) - val surfaceColor = animatingColorTransitionFactory( + val surfaceColor = colorTransitionFactory( bgColor, ::surfaceFromScheme ) { surfaceColor -> val colorList = ColorStateList.valueOf(surfaceColor) mediaViewHolder.player.backgroundTintList = colorList + mediaViewHolder.albumView.foregroundTintList = colorList + mediaViewHolder.albumView.backgroundTintList = colorList mediaViewHolder.seamlessIcon.imageTintList = colorList mediaViewHolder.seamlessText.setTextColor(surfaceColor) mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) } - val accentPrimary = animatingColorTransitionFactory( + val accentPrimary = colorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::accentPrimaryFromScheme ) { accentPrimary -> @@ -134,7 +116,7 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) } - val textPrimary = animatingColorTransitionFactory( + val textPrimary = colorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), ::textPrimaryFromScheme ) { textPrimary -> @@ -150,65 +132,28 @@ class ColorSchemeTransition internal constructor( mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) } - val textPrimaryInverse = animatingColorTransitionFactory( + val textPrimaryInverse = colorTransitionFactory( loadDefaultColor(R.attr.textColorPrimaryInverse), ::textPrimaryInverseFromScheme ) { textPrimaryInverse -> mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse) } - val textSecondary = animatingColorTransitionFactory( + val textSecondary = colorTransitionFactory( loadDefaultColor(R.attr.textColorSecondary), ::textSecondaryFromScheme ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) } - val textTertiary = animatingColorTransitionFactory( + val textTertiary = colorTransitionFactory( loadDefaultColor(R.attr.textColorTertiary), ::textTertiaryFromScheme ) { textTertiary -> mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary) } - // Note: This background gradient currently doesn't animate between colors. - val backgroundGradient = genericColorTransitionFactory { scheme -> - val defaultTintColor = ColorStateList.valueOf(bgColor) - if (scheme == null) { - mediaViewHolder.albumView.foregroundTintList = defaultTintColor - mediaViewHolder.albumView.backgroundTintList = defaultTintColor - return@genericColorTransitionFactory - } - - // If there's no album art, just hide the gradient so we show the solid background. - val showGradient = mediaViewHolder.albumView.drawable != null - val startColor = getColorWithAlpha( - backgroundStartFromScheme(scheme), - alpha = if (showGradient) .25f else 0f - ) - val endColor = getColorWithAlpha( - backgroundEndFromScheme(scheme), - alpha = if (showGradient) .90f else 0f - ) - val gradientColors = intArrayOf(startColor, endColor) - - val foregroundGradient = mediaViewHolder.albumView.foreground.mutate() - if (foregroundGradient is GradientDrawable) { - foregroundGradient.colors = gradientColors - } - val backgroundGradient = mediaViewHolder.albumView.background.mutate() - if (backgroundGradient is GradientDrawable) { - backgroundGradient.colors = gradientColors - } - } - val colorTransitions = arrayOf( - surfaceColor, - accentPrimary, - textPrimary, - textPrimaryInverse, - textSecondary, - textTertiary, - backgroundGradient - ) + surfaceColor, accentPrimary, textPrimary, + textPrimaryInverse, textSecondary, textTertiary) private fun loadDefaultColor(id: Int): Int { return Utils.getColorAttr(context, id).defaultColor diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt index 5e767b0458b9..97c6014c91bd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt @@ -35,9 +35,3 @@ internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] / /** Returns the tertiary text color for media controls based on the scheme. */ internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400 - -/** Returns the color for the start of the background gradient based on the scheme. */ -internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700 - -/** Returns the color for the end of the background gradient based on the scheme. */ -internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700 diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index fe4cb7182168..d4e164208167 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -67,7 +67,7 @@ class PrivacyDialog( attributes.receiveInsetsIgnoringZOrder = true setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) } - + setTitle(R.string.ongoing_privacy_dialog_a11y_title) setContentView(R.layout.privacy_dialog) rootView = requireViewById<ViewGroup>(R.id.root) diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java index 2959c3b30eec..592da6554b90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.carrier; +import android.annotation.StyleRes; import android.content.Context; import android.content.res.ColorStateList; import android.text.TextUtils; @@ -30,6 +31,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import java.util.Objects; @@ -146,4 +148,8 @@ public class QSCarrier extends LinearLayout { public void setCarrierText(CharSequence text) { mCarrierText.setText(text); } + + public void updateTextAppearance(@StyleRes int resId) { + FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java index d03563ffb342..a36035b99b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java @@ -16,12 +16,14 @@ package com.android.systemui.qs.carrier; +import android.annotation.StyleRes; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; /** @@ -55,4 +57,11 @@ public class QSCarrierGroup extends LinearLayout { View getCarrierDivider2() { return findViewById(R.id.qs_carrier_divider2); } + + public void updateTextAppearance(@StyleRes int resId) { + FontSizeUtils.updateFontSizeFromStyle(getNoSimTextView(), resId); + getCarrier1View().updateTextAppearance(resId); + getCarrier2View().updateTextAppearance(resId); + getCarrier3View().updateTextAppearance(resId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 8ca095d9a609..6eb54f799a24 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -24,7 +24,6 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.telephony.ServiceState; @@ -90,8 +89,6 @@ public class InternetDialog extends SystemUIDialog implements @VisibleForTesting protected InternetAdapter mAdapter; @VisibleForTesting - protected WifiManager mWifiManager; - @VisibleForTesting protected View mDialogView; @VisibleForTesting protected boolean mCanConfigWifi; @@ -179,7 +176,6 @@ public class InternetDialog extends SystemUIDialog implements mSubscriptionManager = mInternetDialogController.getSubscriptionManager(); mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId(); mTelephonyManager = mInternetDialogController.getTelephonyManager(); - mWifiManager = mInternetDialogController.getWifiManager(); mCanConfigMobileData = canConfigMobileData; mCanConfigWifi = canConfigWifi; mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context); @@ -332,7 +328,7 @@ public class InternetDialog extends SystemUIDialog implements showProgressBar(); final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked(); - final boolean isWifiEnabled = mWifiManager != null && mWifiManager.isWifiEnabled(); + final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled(); updateWifiToggle(isWifiEnabled, isDeviceLocked); updateConnectedWifi(isWifiEnabled, isDeviceLocked); @@ -362,9 +358,8 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { - if (mWifiManager == null) return; - buttonView.setChecked(isChecked); - mWifiManager.setWifiEnabled(isChecked); + if (mInternetDialogController.isWifiEnabled() == isChecked) return; + mInternetDialogController.setWifiEnabled(isChecked); }); mDoneButton.setOnClickListener(v -> dismiss()); mAirplaneModeButton.setOnClickListener(v -> { @@ -388,7 +383,7 @@ public class InternetDialog extends SystemUIDialog implements Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive); } - boolean isWifiEnabled = mWifiManager != null && mWifiManager.isWifiEnabled(); + boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); if (!mInternetDialogController.hasActiveSubId() && (!isWifiEnabled || !isCarrierNetworkActive)) { mMobileNetworkLayout.setVisibility(View.GONE); @@ -444,7 +439,9 @@ public class InternetDialog extends SystemUIDialog implements @MainThread private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) { - mWiFiToggle.setChecked(isWifiEnabled); + if (mWiFiToggle.isChecked() != isWifiEnabled) { + mWiFiToggle.setChecked(isWifiEnabled); + } if (isDeviceLocked) { mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null) ? R.style.TextAppearance_InternetDialog_Active @@ -572,7 +569,7 @@ public class InternetDialog extends SystemUIDialog implements } protected void showProgressBar() { - if (mWifiManager == null || !mWifiManager.isWifiEnabled() + if (!mInternetDialogController.isWifiEnabled() || mInternetDialogController.isDeviceLocked()) { setProgressBarVisible(false); return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index d97ce7757d8c..90a3d4586fd3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -22,6 +22,7 @@ import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.annotation.AnyThread; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -157,6 +158,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi private LocationController mLocationController; private DialogLaunchAnimator mDialogLaunchAnimator; private boolean mHasWifiEntries; + private WifiStateWorker mWifiStateWorker; @VisibleForTesting static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; @@ -210,7 +212,9 @@ public class InternetDialogController implements AccessPointController.AccessPoi @Background Handler workerHandler, CarrierConfigTracker carrierConfigTracker, LocationController locationController, - DialogLaunchAnimator dialogLaunchAnimator) { + DialogLaunchAnimator dialogLaunchAnimator, + WifiStateWorker wifiStateWorker + ) { if (DEBUG) { Log.d(TAG, "Init InternetDialogController"); } @@ -241,6 +245,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mLocationController = locationController; mDialogLaunchAnimator = dialogLaunchAnimator; mConnectedWifiInternetMonitor = new ConnectedWifiInternetMonitor(); + mWifiStateWorker = wifiStateWorker; } void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) { @@ -323,7 +328,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi @Nullable CharSequence getSubtitleText(boolean isProgressBarVisible) { - if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) { + if (mCanConfigWifi && !isWifiEnabled()) { // When Wi-Fi is disabled. // Sub-Title: Wi-Fi is off if (DEBUG) { @@ -648,6 +653,27 @@ public class InternetDialogController implements AccessPointController.AccessPoi startActivity(intent, view); } + /** + * Enable or disable Wi-Fi. + * + * @param enabled {@code true} to enable, {@code false} to disable. + */ + @AnyThread + public void setWifiEnabled(boolean enabled) { + mWifiStateWorker.setWifiEnabled(enabled); + } + + /** + * Return whether Wi-Fi is enabled or disabled. + * + * @return {@code true} if Wi-Fi is enabled or enabling + * @see WifiManager#getWifiState() + */ + @AnyThread + public boolean isWifiEnabled() { + return mWifiStateWorker.isWifiEnabled(); + } + void connectCarrierNetwork() { final MergedCarrierEntry mergedCarrierEntry = mAccessPointController.getMergedCarrierEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/WifiStateWorker.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/WifiStateWorker.java new file mode 100644 index 000000000000..a7ea50e5e6dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/WifiStateWorker.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.dialog; + +import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE; +import static android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.wifi.WifiManager; +import android.util.Log; + +import androidx.annotation.AnyThread; +import androidx.annotation.Nullable; + +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import javax.inject.Inject; + +/** + * Worker for the Wi-Fi enabled state cache. + */ +@SysUISingleton +public class WifiStateWorker extends BroadcastReceiver { + + private static final String TAG = "WifiStateWorker"; + + private DelayableExecutor mBackgroundExecutor; + private WifiManager mWifiManager; + private int mWifiState = WIFI_STATE_DISABLED; + + @Inject + public WifiStateWorker( + BroadcastDispatcher broadcastDispatcher, + @Background DelayableExecutor backgroundExecutor, + @Nullable WifiManager wifiManager) { + mWifiManager = wifiManager; + mBackgroundExecutor = backgroundExecutor; + + broadcastDispatcher.registerReceiver(this, new IntentFilter(WIFI_STATE_CHANGED_ACTION)); + mBackgroundExecutor.execute(() -> { + if (mWifiManager == null) return; + + mWifiState = mWifiManager.getWifiState(); + Log.i(TAG, "WifiManager.getWifiState():" + mWifiState); + }); + } + + /** + * Enable or disable Wi-Fi. + * + * @param enabled {@code true} to enable, {@code false} to disable. + */ + @AnyThread + public void setWifiEnabled(boolean enabled) { + mBackgroundExecutor.execute(() -> { + if (mWifiManager == null) return; + + mWifiState = (enabled) ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING; + if (!mWifiManager.setWifiEnabled(enabled)) { + Log.e(TAG, "Failed to WifiManager.setWifiEnabled(" + enabled + ");"); + } + }); + } + + /** + * Gets the Wi-Fi enabled state. + * + * @return One of {@link WifiManager#WIFI_STATE_DISABLED}, + * {@link WifiManager#WIFI_STATE_DISABLING}, {@link WifiManager#WIFI_STATE_ENABLED}, + * {@link WifiManager#WIFI_STATE_ENABLING} + */ + @AnyThread + public int getWifiState() { + return mWifiState; + } + + /** + * Return whether Wi-Fi is enabled or disabled. + * + * @return {@code true} if Wi-Fi is enabled or enabling + * @see WifiManager#getWifiState() + */ + @AnyThread + public boolean isWifiEnabled() { + return (mWifiState == WIFI_STATE_ENABLED || mWifiState == WIFI_STATE_ENABLING); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) return; + + if (WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { + final int wifiState = intent.getIntExtra(EXTRA_WIFI_STATE, WIFI_STATE_DISABLED); + if (wifiState == WIFI_STATE_UNKNOWN) return; + + mWifiState = wifiState; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 924351df3117..7f3758e208db 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -27,6 +27,7 @@ import static com.android.systemui.screenshot.LogConfig.logTag; import android.annotation.MainThread; import android.app.Service; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -42,9 +43,11 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.view.WindowManager; +import android.widget.Toast; import androidx.annotation.NonNull; @@ -62,9 +65,11 @@ public class TakeScreenshotService extends Service { private ScreenshotController mScreenshot; private final UserManager mUserManager; + private final DevicePolicyManager mDevicePolicyManager; private final UiEventLogger mUiEventLogger; private final ScreenshotNotificationsController mNotificationsController; private final Handler mHandler; + private final Context mContext; private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() { @Override @@ -91,16 +96,18 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager, - UiEventLogger uiEventLogger, - ScreenshotNotificationsController notificationsController) { + DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger, + ScreenshotNotificationsController notificationsController, Context context) { if (DEBUG_SERVICE) { Log.d(TAG, "new " + this); } mHandler = new Handler(Looper.getMainLooper(), this::handleMessage); mScreenshot = screenshotController; mUserManager = userManager; + mDevicePolicyManager = devicePolicyManager; mUiEventLogger = uiEventLogger; mNotificationsController = notificationsController; + mContext = context; } @Override @@ -182,6 +189,14 @@ public class TakeScreenshotService extends Service { requestCallback.reportError(); return true; } + if(mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { + Log.w(TAG, "Skipping screenshot because an IT admin has disabled " + + "screenshots on the device"); + Toast.makeText(mContext, R.string.screenshot_blocked_by_admin, + Toast.LENGTH_SHORT).show(); + requestCallback.reportError(); + return true; + } ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 0a616c095551..270bdc785178 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -17,7 +17,6 @@ import android.util.MathUtils.lerp import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold -import com.android.systemui.util.getColorWithAlpha import java.util.function.Consumer /** @@ -368,7 +367,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } if (startColorAlpha > 0f) { - canvas.drawColor(getColorWithAlpha(revealGradientEndColor, startColorAlpha)) + canvas.drawColor(updateColorAlpha(revealGradientEndColor, startColorAlpha)) } with(shaderGradientMatrix) { @@ -384,7 +383,15 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, private fun setPaintColorFilter() { gradientPaint.colorFilter = PorterDuffColorFilter( - getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha), + updateColorAlpha(revealGradientEndColor, revealGradientEndColorAlpha), PorterDuff.Mode.MULTIPLY) } + + private fun updateColorAlpha(color: Int, alpha: Float): Int = + Color.argb( + (alpha * 255).toInt(), + Color.red(color), + Color.green(color), + Color.blue(color) + ) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 0b46f07f8e8b..e4f42b10dab0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3178,12 +3178,6 @@ public class NotificationPanelViewController extends PanelViewController { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); super.onTrackingStarted(); mScrimController.onTrackingStarted(); - // normally we want to set mQsExpandImmediate for every split shade case (at least when - // expanding), but keyguard tracking logic is different - this callback is called when - // unlocking with swipe up but not when swiping down to reveal shade - if (mShouldUseSplitNotificationShade && !mKeyguardShowing) { - mQsExpandImmediate = true; - } if (mQsFullyExpanded) { mQsExpandImmediate = true; setShowShelfOnly(true); @@ -4930,6 +4924,12 @@ public class NotificationPanelViewController extends PanelViewController { mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } if (state == STATE_OPENING) { + // we need to ignore it on keyguard as this is a false alarm - transition from unlocked + // to locked will trigger this event and we're not actually in the process of opening + // the shade, lockscreen is just always expanded + if (mShouldUseSplitNotificationShade && !isOnKeyguard()) { + mQsExpandImmediate = true; + } mCentralSurfaces.makeExpandedVisible(false); } if (state == STATE_CLOSED) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 2a9048a6eb73..169347a5ac1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -78,6 +78,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> private final UiEventLogger mUiEventLogger; @VisibleForTesting UserAvatarView mUserAvatarView; + private View mUserAvatarViewWithBackground; UserSwitcherController.UserRecord mCurrentUser; private boolean mIsKeyguardShowing; @@ -167,6 +168,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> super.onInit(); if (DEBUG) Log.d(TAG, "onInit"); mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); + mUserAvatarViewWithBackground = mView.findViewById( + R.id.kg_multi_user_avatar_with_background); mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { @@ -186,7 +189,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUiEventLogger.log( LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); - mUserSwitchDialogController.showDialog(mView); + mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground); }); mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7920d388c670..a50d3d607aec 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -474,7 +474,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { mThemeStyle = fetchThemeStyleFromSetting(); mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); - if (colorSchemeIsApplied()) { + if (colorSchemeIsApplied() && !forceReload) { Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme); return; } diff --git a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt deleted file mode 100644 index 27a53bf2ceda..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import android.content.res.TypedArray -import android.graphics.Color -import android.view.ContextThemeWrapper - -/** Returns an ARGB color version of [color] at the given [alpha]. */ -fun getColorWithAlpha(color: Int, alpha: Float): Int = - Color.argb( - (alpha * 255).toInt(), - Color.red(color), - Color.green(color), - Color.blue(color) - ) - - -/** - * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists, - * otherwise, returns the color from the private attribute {@param privAttrId}. - */ -fun getPrivateAttrColorIfUnset( - ctw: ContextThemeWrapper, attrArray: TypedArray, - attrIndex: Int, defColor: Int, privAttrId: Int -): Int { - // If the index is specified, use that value - var a = attrArray - if (a.hasValue(attrIndex)) { - return a.getColor(attrIndex, defColor) - } - - // Otherwise fallback to the value of the private attribute - val customAttrs = intArrayOf(privAttrId) - a = ctw.obtainStyledAttributes(customAttrs) - val color = a.getColor(0, defColor) - a.recycle() - return color -} diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 5b5dca30620a..8e5e1d2e1b87 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -105,6 +105,25 @@ public class Utils { } /** + * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists, + * otherwise, returns the color from the private attribute {@param privAttrId}. + */ + public static int getPrivateAttrColorIfUnset(ContextThemeWrapper ctw, TypedArray a, + int attrIndex, int defColor, int privAttrId) { + // If the index is specified, use that value + if (a.hasValue(attrIndex)) { + return a.getColor(attrIndex, defColor); + } + + // Otherwise fallback to the value of the private attribute + int[] customAttrs = { privAttrId }; + a = ctw.obtainStyledAttributes(customAttrs); + int color = a.getColor(0, defColor); + a.recycle(); + return color; + } + + /** * Gets the {@link R.dimen#status_bar_header_height_keyguard}. */ public static int getStatusBarHeaderHeightKeyguard(Context context) { diff --git a/packages/SystemUI/tests/src/com/android/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/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt index 65d501442d87..8f967ab5294f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.media import org.mockito.Mockito.`when` as whenever import android.animation.ValueAnimator import android.graphics.Color +import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.monet.ColorScheme import junit.framework.Assert.assertEquals @@ -46,35 +46,28 @@ class ColorSchemeTransitionTest : SysuiTestCase() { private interface ExtractCB : (ColorScheme) -> Int private interface ApplyCB : (Int) -> Unit - private lateinit var colorTransition: AnimatingColorTransition + private lateinit var colorTransition: ColorTransition private lateinit var colorSchemeTransition: ColorSchemeTransition - @Mock private lateinit var mockAnimatingTransition: AnimatingColorTransition - @Mock private lateinit var mockGenericTransition: GenericColorTransition + @Mock private lateinit var mockTransition: ColorTransition @Mock private lateinit var valueAnimator: ValueAnimator @Mock private lateinit var colorScheme: ColorScheme @Mock private lateinit var extractColor: ExtractCB @Mock private lateinit var applyColor: ApplyCB - private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory - private lateinit var genericColorTransitionFactory: GenericColorTransitionFactory + private lateinit var transitionFactory: ColorTransitionFactory @Mock private lateinit var mediaViewHolder: MediaViewHolder @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @Before fun setUp() { - animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition } - genericColorTransitionFactory = { _ -> mockGenericTransition } + transitionFactory = { default, extractColor, applyColor -> mockTransition } whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) - colorSchemeTransition = ColorSchemeTransition( - context, mediaViewHolder, animatingColorTransitionFactory, genericColorTransitionFactory - ) + colorSchemeTransition = ColorSchemeTransition(context, mediaViewHolder, transitionFactory) - colorTransition = object : AnimatingColorTransition( - DEFAULT_COLOR, extractColor, applyColor - ) { + colorTransition = object : ColorTransition(DEFAULT_COLOR, extractColor, applyColor) { override fun buildAnimator(): ValueAnimator { return valueAnimator } @@ -149,7 +142,6 @@ class ColorSchemeTransitionTest : SysuiTestCase() { @Test fun testColorSchemeTransition_update() { colorSchemeTransition.updateColorScheme(colorScheme) - verify(mockAnimatingTransition, times(6)).updateColorScheme(colorScheme) - verify(mockGenericTransition).updateColorScheme(colorScheme) + verify(mockTransition, times(6)).updateColorScheme(colorScheme) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/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/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index cad603c85bbc..f51c428be28a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -352,6 +352,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private FalsingManagerFake mFalsingManager = new FalsingManagerFake(); private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private Handler mMainHandler; + private final PanelExpansionStateManager mPanelExpansionStateManager = + new PanelExpansionStateManager(); @Before public void setup() { @@ -516,7 +518,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mLargeScreenShadeHeaderController, mScreenOffAnimationController, mLockscreenGestureLogger, - new PanelExpansionStateManager(), + mPanelExpansionStateManager, mNotificationRemoteInputManager, mSysUIUnfoldComponent, mControlsComponent, @@ -1018,15 +1020,36 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testQsToBeImmediatelyExpandedInSplitShade() { + public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { enableSplitShade(/* enabled= */ true); + // set panel state to CLOSED + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0, + /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0); + assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); - mNotificationPanelViewController.onTrackingStarted(); + // change panel state to OPENING + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0.5f, + /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 100); assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue(); } @Test + public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() { + enableSplitShade(/* enabled= */ true); + // set panel state to CLOSED + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0, + /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0); + + // go to lockscreen, which also sets fraction to 1.0f and makes shade "expanded" + mStatusBarStateController.setState(KEYGUARD); + mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, + /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 0); + + assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); + } + + @Test public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { mNotificationPanelViewController.mQs = mQs; when(mQsFrame.getX()).thenReturn(0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt index 146b56e49e65..16a326869562 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt @@ -23,6 +23,10 @@ class FakeConfigurationController : ConfigurationController { listeners.forEach { it.onThemeChanged() } } + fun notifyDensityOrFontScaleChanged() { + listeners.forEach { it.onDensityOrFontScaleChanged() } + } + fun notifyConfigurationChanged() { onConfigurationChanged(newConfiguration = null) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 3dfc94bcd5b6..c625dc7d4b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -729,6 +729,18 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); + reset(mResources); + when(mResources.getColor(eq(android.R.color.system_accent1_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6)); + when(mResources.getColor(eq(android.R.color.system_accent2_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6)); + when(mResources.getColor(eq(android.R.color.system_accent3_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6)); + when(mResources.getColor(eq(android.R.color.system_neutral1_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6)); + when(mResources.getColor(eq(android.R.color.system_neutral2_500), any())) + .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6)); + // Defers event because we already have initial colors. verify(mThemeOverlayApplier, never()) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index c48ff9f9f2cc..2dadcecc9f1f 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -97,6 +97,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, DeviceConfig.NAMESPACE_TETHERING, + DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, }; diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e14527098a72..dbe4fb8c8795 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -862,8 +862,8 @@ public class AudioDeviceInventory { } /*package*/ void disconnectLeAudio(int device) { - if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET || - device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) { + if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET + && device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) { Log.e(TAG, "disconnectLeAudio: Can't disconnect not LE Audio device " + device); return; } @@ -879,6 +879,8 @@ public class AudioDeviceInventory { new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") .record(); if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt(device, 0, + AudioSystem.DEVICE_NONE); toRemove.stream().forEach(deviceAddress -> makeLeAudioDeviceUnavailable(deviceAddress, device) ); diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index d10ed55281ef..0aa9a2bc4990 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -462,6 +462,7 @@ public class BtHelper { mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEADSET); mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEARING_AID); mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO); + mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO_BROADCAST); } // @GuardedBy("AudioDeviceBroker.mSetModeLock") @@ -687,6 +688,7 @@ public class BtHelper { case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: + case BluetoothProfile.LE_AUDIO_BROADCAST: mDeviceBroker.postBtProfileDisconnected(profile); break; diff --git a/services/core/java/com/android/server/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/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/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/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/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 0311524cd768..284c29ecfbe9 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; @@ -1093,6 +1094,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/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 89ac9e773906..14abc9aabc29 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -66,11 +66,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 +108,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 +166,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 +388,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 +419,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 */ 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b6a1784839de..b2f9b52cad8d 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; } @@ -2852,7 +2863,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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 68a09a6d4b9b..4f015d8d77a0 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -683,6 +683,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 +845,6 @@ public class AppTransition implements Dump { } setAppTransitionFinishedCallbackIfNeeded(a); - if (mNextAppTransitionBackgroundColor != 0) { - a.setBackdropColor(mNextAppTransitionBackgroundColor); - } - return a; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index dc441860f7c8..8f417c9ad47f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4291,18 +4291,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 +4321,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()); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 014bb9f5e6ad..62998cb6bc40 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -300,10 +300,6 @@ public class DisplayPolicy { // needs to be opaque. private WindowState mNavBarBackgroundWindow; - // The window that draws fake rounded corners and should provide insets to calculate the correct - // rounded corner insets. - private WindowState mRoundedCornerWindow; - /** * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies * which appearance. @@ -970,16 +966,10 @@ public class DisplayPolicy { mExtraNavBarAltPosition = getAltBarPosition(attrs); } - if (attrs.insetsRoundedCornerFrame) { - // Currently, only support one rounded corner window which is the TaskBar. - if (mRoundedCornerWindow != null && mRoundedCornerWindow != win) { - throw new IllegalArgumentException("Found multiple rounded corner window :" - + " current = " + mRoundedCornerWindow - + " new = " + win); - } - mRoundedCornerWindow = win; - } else if (mRoundedCornerWindow == win) { - mRoundedCornerWindow = null; + final InsetsSourceProvider provider = win.getControllableInsetProvider(); + if (provider != null && provider.getSource().getInsetsRoundedCornerFrame() + != attrs.insetsRoundedCornerFrame) { + provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame); } } @@ -1326,9 +1316,6 @@ public class DisplayPolicy { if (mLastFocusedWindow == win) { mLastFocusedWindow = null; } - if (mRoundedCornerWindow == win) { - mRoundedCornerWindow = null; - } mInsetsSourceWindowsExceptIme.remove(win); } @@ -1360,10 +1347,6 @@ public class DisplayPolicy { return mNavigationBar != null ? mNavigationBar : mNavigationBarAlt; } - WindowState getRoundedCornerWindow() { - return mRoundedCornerWindow; - } - /** * Control the animation to run when a window's state changes. Return a positive number to * force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 5aacb094207e..f833773cd5c6 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -702,17 +702,17 @@ public class DisplayRotation { } boolean canRotateSeamlessly(int oldRotation, int newRotation) { + // If the navigation bar can't change sides, then it will jump when we change orientations + // and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation + // where the navbar is low-profile enough that this isn't very noticeable. + if (mAllowSeamlessRotationDespiteNavBarMoving || mDisplayPolicy.navigationBarCanMove()) { + return true; + } // For the upside down rotation we don't rotate seamlessly as the navigation bar moves // position. Note most apps (using orientation:sensor or user as opposed to fullSensor) // will not enter the reverse portrait orientation, so actually the orientation won't change // at all. - if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) { - return false; - } - // If the navigation bar can't change sides, then it will jump when we change orientations - // and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation - // where the navbar is low-profile enough that this isn't very noticeable. - return mAllowSeamlessRotationDespiteNavBarMoving || mDisplayPolicy.navigationBarCanMove(); + return oldRotation != Surface.ROTATION_180 && newRotation != Surface.ROTATION_180; } void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) { @@ -1224,16 +1224,8 @@ public class DisplayRotation { || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { // Otherwise, use sensor only if requested by the application or enabled // by default for USER or UNSPECIFIED modes. Does not apply to NOSENSOR. - if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) { - // Can't read this during init() because the context doesn't have display metrics at - // that time so we cannot determine tablet vs. phone then. - mAllowAllRotations = mContext.getResources().getBoolean( - R.bool.config_allowAllRotations) - ? ALLOW_ALL_ROTATIONS_ENABLED - : ALLOW_ALL_ROTATIONS_DISABLED; - } if (sensorRotation != Surface.ROTATION_180 - || mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED + || getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) { preferredRotation = sensorRotation; @@ -1322,6 +1314,19 @@ public class DisplayRotation { } } + private int getAllowAllRotations() { + if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) { + // Can't read this during init() because the context doesn't have display metrics at + // that time so we cannot determine tablet vs. phone then. + mAllowAllRotations = mContext.getResources().getBoolean( + R.bool.config_allowAllRotations) + ? ALLOW_ALL_ROTATIONS_ENABLED + : ALLOW_ALL_ROTATIONS_DISABLED; + } + + return mAllowAllRotations; + } + private boolean isLandscapeOrSeascape(int rotation) { return rotation == mLandscapeRotation || rotation == mSeascapeRotation; } @@ -1349,6 +1354,11 @@ public class DisplayRotation { case ActivityInfo.SCREEN_ORIENTATION_USER: case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: + // When all rotations enabled it works with any of the 4 rotations + if (getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED) { + return preferredRotation >= 0; + } + // Works with any rotation except upside down. return (preferredRotation >= 0) && (preferredRotation != Surface.ROTATION_180); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 6162f12b516e..5c8cfffdd3b3 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -46,7 +46,6 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.StatusBarManager; import android.app.WindowConfiguration; -import android.graphics.Insets; import android.graphics.Rect; import android.util.ArrayMap; import android.util.IntArray; @@ -461,22 +460,10 @@ class InsetsPolicy { private InsetsState adjustInsetsForRoundedCorners(WindowState w, InsetsState originalState, boolean copyState) { - final WindowState roundedCornerWindow = mPolicy.getRoundedCornerWindow(); final Task task = w.getTask(); - if (task != null && !task.getWindowConfiguration().tasksAreFloating() - && (roundedCornerWindow != null || task.inSplitScreen())) { - // Instead of using display frame to calculating rounded corner, for the fake rounded - // corners drawn by divider bar or task bar, we need to re-calculate rounded corners - // based on task bounds and if the task bounds is intersected with task bar, we should - // exclude the intersected part. + if (task != null && !task.getWindowConfiguration().tasksAreFloating()) { + // Use task bounds to calculating rounded corners if the task is not floating. final Rect roundedCornerFrame = new Rect(task.getBounds()); - if (roundedCornerWindow != null - && roundedCornerWindow.getControllableInsetProvider() != null) { - final InsetsSource source = - roundedCornerWindow.getControllableInsetProvider().getSource(); - final Insets insets = source.calculateInsets(roundedCornerFrame, false); - roundedCornerFrame.inset(insets); - } final InsetsState state = copyState ? new InsetsState(originalState) : originalState; state.setRoundedCornerFrame(roundedCornerFrame); return state; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 8413c5442536..9853d1304b14 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -170,6 +170,7 @@ abstract class InsetsSourceProvider { if (windowContainer == null) { setServerVisible(false); mSource.setVisibleFrame(null); + mSource.setInsetsRoundedCornerFrame(false); mSourceFrame.setEmpty(); } else { mWindowContainer.getProvidedInsetsSources().put(mSource.getType(), mSource); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f97f768872fd..17e1dd26a602 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1724,8 +1724,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) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 73a755dd3123..1176182ede50 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -963,7 +963,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } else if (candidateTask != null) { final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; final Task launchRootTask = getLaunchRootTask(resolvedWindowingMode, activityType, - options, sourceTask, launchFlags); + options, sourceTask, launchFlags, candidateTask); if (launchRootTask != null) { if (candidateTask.getParent() == null) { launchRootTask.addChild(candidateTask, position); @@ -1117,6 +1117,13 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { @Nullable Task getLaunchRootTask(int windowingMode, int activityType, @Nullable ActivityOptions options, @Nullable Task sourceTask, int launchFlags) { + return getLaunchRootTask(windowingMode, activityType, options, sourceTask, launchFlags, + null /* candidateTask */); + } + + @Nullable + Task getLaunchRootTask(int windowingMode, int activityType, @Nullable ActivityOptions options, + @Nullable Task sourceTask, int launchFlags, @Nullable Task candidateTask) { // Try to use the launch root task in options if available. if (options != null) { final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); @@ -1157,9 +1164,19 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } // For a better split UX, If a task is launching from a created-by-organizer task, it should - // be launched into the same created-by-organizer task as well. - if (sourceTask != null) { - return sourceTask.getCreatedByOrganizerTask(); + // be launched into the same created-by-organizer task as well. Unless, the candidate task + // is already positioned in the split. + Task preferredRootInSplit = sourceTask != null && sourceTask.inSplitScreen() + ? sourceTask.getCreatedByOrganizerTask() : null; + if (preferredRootInSplit != null) { + if (candidateTask != null) { + final Task candidateRoot = candidateTask.getCreatedByOrganizerTask(); + if (candidateRoot != null && candidateRoot != preferredRootInSplit + && preferredRootInSplit == candidateRoot.getAdjacentTaskFragment()) { + preferredRootInSplit = candidateRoot; + } + } + return preferredRootInSplit; } return null; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 4cb4e9175534..56e96fa1fe58 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2156,7 +2156,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { return applicationType; } - return getTopChild().getActivityType(); + final ActivityRecord activity = getTopNonFinishingActivity(); + return activity != null ? activity.getActivityType() : getTopChild().getActivityType(); } @Override diff --git a/services/core/java/com/android/server/wm/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/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/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..67f02c7fab55 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -46,7 +46,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 +88,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 +126,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 +144,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, 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/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 80f6bceb884c..e5e0145095c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -741,4 +741,35 @@ public class TaskDisplayAreaTests extends WindowTestsBase { assertEquals(isAssistantOnTop ? topPosition : topPosition - 4, getTaskIndexOf(taskDisplayArea, assistRootTask)); } + + /** + * This test verifies proper launch root based on source and candidate task for split screen. + * If a task is launching from a created-by-organizer task, it should be launched into the + * same created-by-organizer task as well. Unless, the candidate task is already positioned in + * the split. + */ + @Test + public void getLaunchRootTaskInSplit() { + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + rootTask.mCreatedByOrganizer = true; + final Task adjacentRootTask = createTask( + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + adjacentRootTask.mCreatedByOrganizer = true; + final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/); + final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); + adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); + + // Verify the launch root with candidate task + Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */, + 0 /* launchFlags */, candidateTask); + assertSame(rootTask, actualRootTask.getRootTask()); + + // Verify the launch root task without candidate task + actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */, + 0 /* launchFlags */); + assertSame(adjacentRootTask, actualRootTask.getRootTask()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index c672b9173570..7507df6eba74 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. |