diff options
562 files changed, 19234 insertions, 6118 deletions
diff --git a/MULTIUSER_OWNERS b/MULTIUSER_OWNERS index 9d92e0fc50f7..b8857ecd0a93 100644 --- a/MULTIUSER_OWNERS +++ b/MULTIUSER_OWNERS @@ -1,5 +1,9 @@ # OWNERS of Multiuser related files +annabauza@google.com bookatz@google.com +nykkumar@google.com olilan@google.com omakoto@google.com +tetianameronyk@google.com +tyk@google.com yamasani@google.com diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java index adcd5710ef19..b995b0688970 100644 --- a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java +++ b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java @@ -158,11 +158,10 @@ public class ExpensiveObjectsPerfTest { } @Test - public void timeClonedSimpleDateFormat() { - SimpleDateFormat sdf = new SimpleDateFormat(); + public void timeNewSimpleDateFormat() { BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - sdf.clone(); + new SimpleDateFormat(); } } diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index c3fc7b16ebdf..be0e02595b60 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -235,6 +235,15 @@ public class EconomyManager { public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit"; // TODO: Add JobScheduler modifier keys /** @hide */ + public static final String KEY_JS_REWARD_APP_INSTALL_INSTANT = + "js_reward_app_install_instant"; + /** @hide */ + public static final String KEY_JS_REWARD_APP_INSTALL_ONGOING = + "js_reward_app_install_ongoing"; + /** @hide */ + public static final String KEY_JS_REWARD_APP_INSTALL_MAX = + "js_reward_app_install_max"; + /** @hide */ public static final String KEY_JS_REWARD_TOP_ACTIVITY_INSTANT = "js_reward_top_activity_instant"; /** @hide */ @@ -463,6 +472,12 @@ public class EconomyManager { public static final long DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000); // TODO: add JobScheduler modifier default values /** @hide */ + public static final long DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES = arcToCake(408); + /** @hide */ + public static final long DEFAULT_JS_REWARD_APP_INSTALL_ONGOING_CAKES = arcToCake(0); + /** @hide */ + public static final long DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES = arcToCake(4000); + /** @hide */ public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0); /** @hide */ public static final long DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES = CAKE_IN_ARC / 2; diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 40d1b4c9b267..bd475e9dd734 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -37,6 +37,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; +import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -993,69 +994,69 @@ public class DeviceIdleController extends SystemService "pre_idle_factor_short"; private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms"; - private static final long DEFAULT_FLEX_TIME_SHORT = + private long mDefaultFlexTimeShort = !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; - private static final long DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = + private long mDefaultLightIdleAfterInactiveTimeout = !COMPRESS_TIME ? 4 * 60 * 1000L : 30 * 1000L; - private static final long DEFAULT_LIGHT_IDLE_TIMEOUT = + private long mDefaultLightIdleTimeout = !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L; - private static final float DEFAULT_LIGHT_IDLE_FACTOR = 2f; - private static final long DEFAULT_LIGHT_MAX_IDLE_TIMEOUT = + private float mDefaultLightIdleFactor = 2f; + private long mDefaultLightMaxIdleTimeout = !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L; - private static final long DEFAULT_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = + private long mDefaultLightIdleMaintenanceMinBudget = !COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L; - private static final long DEFAULT_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = + private long mDefaultLightIdleMaintenanceMaxBudget = !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L; - private static final long DEFAULT_MIN_LIGHT_MAINTENANCE_TIME = + private long mDefaultMinLightMaintenanceTime = !COMPRESS_TIME ? 5 * 1000L : 1 * 1000L; - private static final long DEFAULT_MIN_DEEP_MAINTENANCE_TIME = + private long mDefaultMinDeepMaintenanceTime = !COMPRESS_TIME ? 30 * 1000L : 5 * 1000L; - private static final long DEFAULT_INACTIVE_TIMEOUT = + private long mDefaultInactiveTimeout = (30 * 60 * 1000L) / (!COMPRESS_TIME ? 1 : 10); private static final long DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY = (15 * 60 * 1000L) / (!COMPRESS_TIME ? 1 : 10); - private static final long DEFAULT_SENSING_TIMEOUT = + private long mDefaultSensingTimeout = !COMPRESS_TIME ? 4 * 60 * 1000L : 60 * 1000L; - private static final long DEFAULT_LOCATING_TIMEOUT = + private long mDefaultLocatingTimeout = !COMPRESS_TIME ? 30 * 1000L : 15 * 1000L; - private static final float DEFAULT_LOCATION_ACCURACY = 20f; - private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = + private float mDefaultLocationAccuracy = 20f; + private long mDefaultMotionInactiveTimeout = !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L; - private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT_FLEX = + private long mDefaultMotionInactiveTimeoutFlex = !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; - private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = + private long mDefaultIdleAfterInactiveTimeout = (30 * 60 * 1000L) / (!COMPRESS_TIME ? 1 : 10); private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY = (15 * 60 * 1000L) / (!COMPRESS_TIME ? 1 : 10); - private static final long DEFAULT_IDLE_PENDING_TIMEOUT = + private long mDefaultIdlePendingTimeout = !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L; - private static final long DEFAULT_MAX_IDLE_PENDING_TIMEOUT = + private long mDefaultMaxIdlePendingTimeout = !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L; - private static final float DEFAULT_IDLE_PENDING_FACTOR = 2f; - private static final long DEFAULT_QUICK_DOZE_DELAY_TIMEOUT = + private float mDefaultIdlePendingFactor = 2f; + private long mDefaultQuickDozeDelayTimeout = !COMPRESS_TIME ? 60 * 1000L : 15 * 1000L; - private static final long DEFAULT_IDLE_TIMEOUT = + private long mDefaultIdleTimeout = !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L; - private static final long DEFAULT_MAX_IDLE_TIMEOUT = + private long mDefaultMaxIdleTimeout = !COMPRESS_TIME ? 6 * 60 * 60 * 1000L : 30 * 60 * 1000L; - private static final float DEFAULT_IDLE_FACTOR = 2f; - private static final long DEFAULT_MIN_TIME_TO_ALARM = + private float mDefaultIdleFactor = 2f; + private long mDefaultMinTimeToAlarm = !COMPRESS_TIME ? 30 * 60 * 1000L : 6 * 60 * 1000L; - private static final long DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS = 5 * 60 * 1000L; - private static final long DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS = 60 * 1000L; - private static final long DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS = 20 * 1000L; - private static final long DEFAULT_NOTIFICATION_ALLOWLIST_DURATION_MS = 30 * 1000L; - private static final boolean DEFAULT_WAIT_FOR_UNLOCK = true; - private static final float DEFAULT_PRE_IDLE_FACTOR_LONG = 1.67f; - private static final float DEFAULT_PRE_IDLE_FACTOR_SHORT = .33f; - private static final boolean DEFAULT_USE_WINDOW_ALARMS = true; + private long mDefaultMaxTempAppAllowlistDurationMs = 5 * 60 * 1000L; + private long mDefaultMmsTempAppAllowlistDurationMs = 60 * 1000L; + private long mDefaultSmsTempAppAllowlistDurationMs = 20 * 1000L; + private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L; + private boolean mDefaultWaitForUnlock = true; + private float mDefaultPreIdleFactorLong = 1.67f; + private float mDefaultPreIdleFactorShort = .33f; + private boolean mDefaultUseWindowAlarms = true; /** * A somewhat short alarm window size that we will tolerate for various alarm timings. * * @see #KEY_FLEX_TIME_SHORT */ - public long FLEX_TIME_SHORT = DEFAULT_FLEX_TIME_SHORT; + public long FLEX_TIME_SHORT = mDefaultFlexTimeShort; /** * This is the time, after becoming inactive, that we go in to the first @@ -1063,28 +1064,28 @@ public class DeviceIdleController extends SystemService * * @see #KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT */ - public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT; + public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout; /** * This is the initial time that we will run in light idle maintenance mode. * * @see #KEY_LIGHT_IDLE_TIMEOUT */ - public long LIGHT_IDLE_TIMEOUT = DEFAULT_LIGHT_IDLE_TIMEOUT; + public long LIGHT_IDLE_TIMEOUT = mDefaultLightIdleTimeout; /** * Scaling factor to apply to the light idle mode time each time we complete a cycle. * * @see #KEY_LIGHT_IDLE_FACTOR */ - public float LIGHT_IDLE_FACTOR = DEFAULT_LIGHT_IDLE_FACTOR; + public float LIGHT_IDLE_FACTOR = mDefaultLightIdleFactor; /** * This is the maximum time we will stay in light idle mode. * * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT */ - public long LIGHT_MAX_IDLE_TIMEOUT = DEFAULT_LIGHT_MAX_IDLE_TIMEOUT; + public long LIGHT_MAX_IDLE_TIMEOUT = mDefaultLightMaxIdleTimeout; /** * This is the minimum amount of time we want to make available for maintenance mode @@ -1093,7 +1094,7 @@ public class DeviceIdleController extends SystemService * * @see #KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET */ - public long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = DEFAULT_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; + public long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mDefaultLightIdleMaintenanceMinBudget; /** * This is the maximum amount of time we want to make available for maintenance mode @@ -1104,7 +1105,7 @@ public class DeviceIdleController extends SystemService * * @see #KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET */ - public long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = DEFAULT_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET; + public long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mDefaultLightIdleMaintenanceMaxBudget; /** * This is the minimum amount of time that we will stay in maintenance mode after @@ -1115,7 +1116,7 @@ public class DeviceIdleController extends SystemService * * @see #KEY_MIN_LIGHT_MAINTENANCE_TIME */ - public long MIN_LIGHT_MAINTENANCE_TIME = DEFAULT_MIN_LIGHT_MAINTENANCE_TIME; + public long MIN_LIGHT_MAINTENANCE_TIME = mDefaultMinLightMaintenanceTime; /** * This is the minimum amount of time that we will stay in maintenance mode after @@ -1125,7 +1126,7 @@ public class DeviceIdleController extends SystemService * mode immediately. * @see #KEY_MIN_DEEP_MAINTENANCE_TIME */ - public long MIN_DEEP_MAINTENANCE_TIME = DEFAULT_MIN_DEEP_MAINTENANCE_TIME; + public long MIN_DEEP_MAINTENANCE_TIME = mDefaultMinDeepMaintenanceTime; /** * This is the time, after becoming inactive, at which we start looking at the @@ -1134,7 +1135,7 @@ public class DeviceIdleController extends SystemService * the motion sensor whenever the screen is off. * @see #KEY_INACTIVE_TIMEOUT */ - public long INACTIVE_TIMEOUT = DEFAULT_INACTIVE_TIMEOUT; + public long INACTIVE_TIMEOUT = mDefaultInactiveTimeout; /** * If we don't receive a callback from AnyMotion in this amount of time + @@ -1143,14 +1144,14 @@ public class DeviceIdleController extends SystemService * will be ignored. * @see #KEY_SENSING_TIMEOUT */ - public long SENSING_TIMEOUT = DEFAULT_SENSING_TIMEOUT; + public long SENSING_TIMEOUT = mDefaultSensingTimeout; /** * This is how long we will wait to try to get a good location fix before going in to * idle mode. * @see #KEY_LOCATING_TIMEOUT */ - public long LOCATING_TIMEOUT = DEFAULT_LOCATING_TIMEOUT; + public long LOCATING_TIMEOUT = mDefaultLocatingTimeout; /** * The desired maximum accuracy (in meters) we consider the location to be good enough to go @@ -1158,7 +1159,7 @@ public class DeviceIdleController extends SystemService * {@link #LOCATING_TIMEOUT} expires. * @see #KEY_LOCATION_ACCURACY */ - public float LOCATION_ACCURACY = DEFAULT_LOCATION_ACCURACY; + public float LOCATION_ACCURACY = mDefaultLocationAccuracy; /** * This is the time, after seeing motion, that we wait after becoming inactive from @@ -1166,14 +1167,14 @@ public class DeviceIdleController extends SystemService * * @see #KEY_MOTION_INACTIVE_TIMEOUT */ - public long MOTION_INACTIVE_TIMEOUT = DEFAULT_MOTION_INACTIVE_TIMEOUT; + public long MOTION_INACTIVE_TIMEOUT = mDefaultMotionInactiveTimeout; /** * This is the alarm window size we will tolerate for motion detection timings. * * @see #KEY_MOTION_INACTIVE_TIMEOUT_FLEX */ - public long MOTION_INACTIVE_TIMEOUT_FLEX = DEFAULT_MOTION_INACTIVE_TIMEOUT_FLEX; + public long MOTION_INACTIVE_TIMEOUT_FLEX = mDefaultMotionInactiveTimeoutFlex; /** * This is the time, after the inactive timeout elapses, that we will wait looking @@ -1181,7 +1182,7 @@ public class DeviceIdleController extends SystemService * * @see #KEY_IDLE_AFTER_INACTIVE_TIMEOUT */ - public long IDLE_AFTER_INACTIVE_TIMEOUT = DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT; + public long IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultIdleAfterInactiveTimeout; /** * This is the initial time, after being idle, that we will allow ourself to be back @@ -1189,20 +1190,20 @@ public class DeviceIdleController extends SystemService * idle. * @see #KEY_IDLE_PENDING_TIMEOUT */ - public long IDLE_PENDING_TIMEOUT = DEFAULT_IDLE_PENDING_TIMEOUT; + public long IDLE_PENDING_TIMEOUT = mDefaultIdlePendingTimeout; /** * Maximum pending idle timeout (time spent running) we will be allowed to use. * @see #KEY_MAX_IDLE_PENDING_TIMEOUT */ - public long MAX_IDLE_PENDING_TIMEOUT = DEFAULT_MAX_IDLE_PENDING_TIMEOUT; + public long MAX_IDLE_PENDING_TIMEOUT = mDefaultMaxIdlePendingTimeout; /** * Scaling factor to apply to current pending idle timeout each time we cycle through * that state. * @see #KEY_IDLE_PENDING_FACTOR */ - public float IDLE_PENDING_FACTOR = DEFAULT_IDLE_PENDING_FACTOR; + public float IDLE_PENDING_FACTOR = mDefaultIdlePendingFactor; /** * This is amount of time we will wait from the point where we go into @@ -1210,33 +1211,33 @@ public class DeviceIdleController extends SystemService * and other current activity to finish. * @see #KEY_QUICK_DOZE_DELAY_TIMEOUT */ - public long QUICK_DOZE_DELAY_TIMEOUT = DEFAULT_QUICK_DOZE_DELAY_TIMEOUT; + public long QUICK_DOZE_DELAY_TIMEOUT = mDefaultQuickDozeDelayTimeout; /** * This is the initial time that we want to sit in the idle state before waking up * again to return to pending idle and allowing normal work to run. * @see #KEY_IDLE_TIMEOUT */ - public long IDLE_TIMEOUT = DEFAULT_IDLE_TIMEOUT; + public long IDLE_TIMEOUT = mDefaultIdleTimeout; /** * Maximum idle duration we will be allowed to use. * @see #KEY_MAX_IDLE_TIMEOUT */ - public long MAX_IDLE_TIMEOUT = DEFAULT_MAX_IDLE_TIMEOUT; + public long MAX_IDLE_TIMEOUT = mDefaultMaxIdleTimeout; /** * Scaling factor to apply to current idle timeout each time we cycle through that state. * @see #KEY_IDLE_FACTOR */ - public float IDLE_FACTOR = DEFAULT_IDLE_FACTOR; + public float IDLE_FACTOR = mDefaultIdleFactor; /** * This is the minimum time we will allow until the next upcoming alarm for us to * actually go in to idle mode. * @see #KEY_MIN_TIME_TO_ALARM */ - public long MIN_TIME_TO_ALARM = DEFAULT_MIN_TIME_TO_ALARM; + public long MIN_TIME_TO_ALARM = mDefaultMinTimeToAlarm; /** * Max amount of time to temporarily whitelist an app when it receives a high priority @@ -1244,48 +1245,49 @@ public class DeviceIdleController extends SystemService * * @see #KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS */ - public long MAX_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS; + public long MAX_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultMaxTempAppAllowlistDurationMs; /** * Amount of time we would like to whitelist an app that is receiving an MMS. * @see #KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS */ - public long MMS_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS; + public long MMS_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultMmsTempAppAllowlistDurationMs; /** * Amount of time we would like to whitelist an app that is receiving an SMS. * @see #KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS */ - public long SMS_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS; + public long SMS_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultSmsTempAppAllowlistDurationMs; /** * Amount of time we would like to whitelist an app that is handling a * {@link android.app.PendingIntent} triggered by a {@link android.app.Notification}. * @see #KEY_NOTIFICATION_ALLOWLIST_DURATION_MS */ - public long NOTIFICATION_ALLOWLIST_DURATION_MS = DEFAULT_NOTIFICATION_ALLOWLIST_DURATION_MS; + public long NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs; /** * Pre idle time factor use to make idle delay longer */ - public float PRE_IDLE_FACTOR_LONG = DEFAULT_PRE_IDLE_FACTOR_LONG; + public float PRE_IDLE_FACTOR_LONG = mDefaultPreIdleFactorLong; /** * Pre idle time factor use to make idle delay shorter */ - public float PRE_IDLE_FACTOR_SHORT = DEFAULT_PRE_IDLE_FACTOR_SHORT; + public float PRE_IDLE_FACTOR_SHORT = mDefaultPreIdleFactorShort; - public boolean WAIT_FOR_UNLOCK = DEFAULT_WAIT_FOR_UNLOCK; + public boolean WAIT_FOR_UNLOCK = mDefaultWaitForUnlock; /** * Whether to use window alarms. True to use window alarms (call AlarmManager.setWindow()). * False to use the legacy inexact alarms (call AlarmManager.set()). */ - public boolean USE_WINDOW_ALARMS = DEFAULT_USE_WINDOW_ALARMS; + public boolean USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; private final boolean mSmallBatteryDevice; public Constants() { + initDefault(); mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice(); if (mSmallBatteryDevice) { INACTIVE_TIMEOUT = DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY; @@ -1297,6 +1299,132 @@ public class DeviceIdleController extends SystemService onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE)); } + private void initDefault() { + final Resources res = getContext().getResources(); + + mDefaultFlexTimeShort = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_flex_time_short_ms), + mDefaultFlexTimeShort); + mDefaultLightIdleAfterInactiveTimeout = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_light_after_inactive_to_ms), + mDefaultLightIdleAfterInactiveTimeout); + mDefaultLightIdleTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_light_idle_to_ms), + mDefaultLightIdleTimeout); + mDefaultLightIdleFactor = res.getFloat( + com.android.internal.R.integer.device_idle_light_idle_factor); + mDefaultLightMaxIdleTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_light_max_idle_to_ms), + mDefaultLightMaxIdleTimeout); + mDefaultLightIdleMaintenanceMinBudget = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_light_idle_maintenance_min_budget_ms + ), mDefaultLightIdleMaintenanceMinBudget); + mDefaultLightIdleMaintenanceMaxBudget = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_light_idle_maintenance_max_budget_ms + ), mDefaultLightIdleMaintenanceMaxBudget); + mDefaultMinLightMaintenanceTime = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_min_light_maintenance_time_ms), + mDefaultMinLightMaintenanceTime); + mDefaultMinDeepMaintenanceTime = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_min_deep_maintenance_time_ms), + mDefaultMinDeepMaintenanceTime); + mDefaultInactiveTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_inactive_to_ms), + mDefaultInactiveTimeout); + mDefaultSensingTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_sensing_to_ms), + mDefaultSensingTimeout); + mDefaultLocatingTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_locating_to_ms), + mDefaultLocatingTimeout); + mDefaultLocationAccuracy = res.getFloat( + com.android.internal.R.integer.device_idle_location_accuracy); + mDefaultMotionInactiveTimeout = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_motion_inactive_to_ms), + mDefaultMotionInactiveTimeout); + mDefaultMotionInactiveTimeoutFlex = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_motion_inactive_to_flex_ms), + mDefaultMotionInactiveTimeoutFlex); + mDefaultIdleAfterInactiveTimeout = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_idle_after_inactive_to_ms), + mDefaultIdleAfterInactiveTimeout); + mDefaultIdlePendingTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_idle_pending_to_ms), + mDefaultIdlePendingTimeout); + mDefaultMaxIdlePendingTimeout = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_max_idle_pending_to_ms), + mDefaultMaxIdlePendingTimeout); + mDefaultIdlePendingFactor = res.getFloat( + com.android.internal.R.integer.device_idle_idle_pending_factor); + mDefaultQuickDozeDelayTimeout = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_quick_doze_delay_to_ms), + mDefaultQuickDozeDelayTimeout); + mDefaultIdleTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_idle_to_ms), + mDefaultIdleTimeout); + mDefaultMaxIdleTimeout = getTimeout( + res.getInteger(com.android.internal.R.integer.device_idle_max_idle_to_ms), + mDefaultMaxIdleTimeout); + mDefaultIdleFactor = res.getFloat( + com.android.internal.R.integer.device_idle_idle_factor); + mDefaultMinTimeToAlarm = getTimeout(res.getInteger( + com.android.internal.R.integer.device_idle_min_time_to_alarm_ms), + mDefaultMinTimeToAlarm); + mDefaultMaxTempAppAllowlistDurationMs = res.getInteger( + com.android.internal.R.integer.device_idle_max_temp_app_allowlist_duration_ms); + mDefaultMmsTempAppAllowlistDurationMs = res.getInteger( + com.android.internal.R.integer.device_idle_mms_temp_app_allowlist_duration_ms); + mDefaultSmsTempAppAllowlistDurationMs = res.getInteger( + com.android.internal.R.integer.device_idle_sms_temp_app_allowlist_duration_ms); + mDefaultNotificationAllowlistDurationMs = res.getInteger( + com.android.internal.R.integer.device_idle_notification_allowlist_duration_ms); + mDefaultWaitForUnlock = res.getBoolean( + com.android.internal.R.bool.device_idle_wait_for_unlock); + mDefaultPreIdleFactorLong = res.getFloat( + com.android.internal.R.integer.device_idle_pre_idle_factor_long); + mDefaultPreIdleFactorShort = res.getFloat( + com.android.internal.R.integer.device_idle_pre_idle_factor_short); + mDefaultUseWindowAlarms = res.getBoolean( + com.android.internal.R.bool.device_idle_use_window_alarms); + + FLEX_TIME_SHORT = mDefaultFlexTimeShort; + LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout; + LIGHT_IDLE_TIMEOUT = mDefaultLightIdleTimeout; + LIGHT_IDLE_FACTOR = mDefaultLightIdleFactor; + LIGHT_MAX_IDLE_TIMEOUT = mDefaultLightMaxIdleTimeout; + LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mDefaultLightIdleMaintenanceMinBudget; + LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mDefaultLightIdleMaintenanceMaxBudget; + MIN_LIGHT_MAINTENANCE_TIME = mDefaultMinLightMaintenanceTime; + MIN_DEEP_MAINTENANCE_TIME = mDefaultMinDeepMaintenanceTime; + INACTIVE_TIMEOUT = mDefaultInactiveTimeout; + SENSING_TIMEOUT = mDefaultSensingTimeout; + LOCATING_TIMEOUT = mDefaultLocatingTimeout; + LOCATION_ACCURACY = mDefaultLocationAccuracy; + MOTION_INACTIVE_TIMEOUT = mDefaultMotionInactiveTimeout; + MOTION_INACTIVE_TIMEOUT_FLEX = mDefaultMotionInactiveTimeoutFlex; + IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultIdleAfterInactiveTimeout; + IDLE_PENDING_TIMEOUT = mDefaultIdlePendingTimeout; + MAX_IDLE_PENDING_TIMEOUT = mDefaultMaxIdlePendingTimeout; + IDLE_PENDING_FACTOR = mDefaultIdlePendingFactor; + QUICK_DOZE_DELAY_TIMEOUT = mDefaultQuickDozeDelayTimeout; + IDLE_TIMEOUT = mDefaultIdleTimeout; + MAX_IDLE_TIMEOUT = mDefaultMaxIdleTimeout; + IDLE_FACTOR = mDefaultIdleFactor; + MIN_TIME_TO_ALARM = mDefaultMinTimeToAlarm; + MAX_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultMaxTempAppAllowlistDurationMs; + MMS_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultMmsTempAppAllowlistDurationMs; + SMS_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultSmsTempAppAllowlistDurationMs; + NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs; + WAIT_FOR_UNLOCK = mDefaultWaitForUnlock; + PRE_IDLE_FACTOR_LONG = mDefaultPreIdleFactorLong; + PRE_IDLE_FACTOR_SHORT = mDefaultPreIdleFactorShort; + USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; + } + + private long getTimeout(long defTimeout, long compTimeout) { + return (!COMPRESS_TIME || defTimeout < compTimeout) ? defTimeout : compTimeout; + } + @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { @@ -1308,147 +1436,147 @@ public class DeviceIdleController extends SystemService switch (name) { case KEY_FLEX_TIME_SHORT: FLEX_TIME_SHORT = properties.getLong( - KEY_FLEX_TIME_SHORT, DEFAULT_FLEX_TIME_SHORT); + KEY_FLEX_TIME_SHORT, mDefaultFlexTimeShort); break; case KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT: LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = properties.getLong( KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, - DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT); + mDefaultLightIdleAfterInactiveTimeout); break; case KEY_LIGHT_IDLE_TIMEOUT: LIGHT_IDLE_TIMEOUT = properties.getLong( - KEY_LIGHT_IDLE_TIMEOUT, DEFAULT_LIGHT_IDLE_TIMEOUT); + KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout); break; case KEY_LIGHT_IDLE_FACTOR: LIGHT_IDLE_FACTOR = Math.max(1, properties.getFloat( - KEY_LIGHT_IDLE_FACTOR, DEFAULT_LIGHT_IDLE_FACTOR)); + KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor)); break; case KEY_LIGHT_MAX_IDLE_TIMEOUT: LIGHT_MAX_IDLE_TIMEOUT = properties.getLong( - KEY_LIGHT_MAX_IDLE_TIMEOUT, DEFAULT_LIGHT_MAX_IDLE_TIMEOUT); + KEY_LIGHT_MAX_IDLE_TIMEOUT, mDefaultLightMaxIdleTimeout); break; case KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET: LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = properties.getLong( KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, - DEFAULT_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); + mDefaultLightIdleMaintenanceMinBudget); break; case KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET: LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = properties.getLong( KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, - DEFAULT_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET); + mDefaultLightIdleMaintenanceMaxBudget); break; case KEY_MIN_LIGHT_MAINTENANCE_TIME: MIN_LIGHT_MAINTENANCE_TIME = properties.getLong( KEY_MIN_LIGHT_MAINTENANCE_TIME, - DEFAULT_MIN_LIGHT_MAINTENANCE_TIME); + mDefaultMinLightMaintenanceTime); break; case KEY_MIN_DEEP_MAINTENANCE_TIME: MIN_DEEP_MAINTENANCE_TIME = properties.getLong( KEY_MIN_DEEP_MAINTENANCE_TIME, - DEFAULT_MIN_DEEP_MAINTENANCE_TIME); + mDefaultMinDeepMaintenanceTime); break; case KEY_INACTIVE_TIMEOUT: final long defaultInactiveTimeout = mSmallBatteryDevice ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY - : DEFAULT_INACTIVE_TIMEOUT; + : mDefaultInactiveTimeout; INACTIVE_TIMEOUT = properties.getLong( KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout); break; case KEY_SENSING_TIMEOUT: SENSING_TIMEOUT = properties.getLong( - KEY_SENSING_TIMEOUT, DEFAULT_SENSING_TIMEOUT); + KEY_SENSING_TIMEOUT, mDefaultSensingTimeout); break; case KEY_LOCATING_TIMEOUT: LOCATING_TIMEOUT = properties.getLong( - KEY_LOCATING_TIMEOUT, DEFAULT_LOCATING_TIMEOUT); + KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout); break; case KEY_LOCATION_ACCURACY: LOCATION_ACCURACY = properties.getFloat( - KEY_LOCATION_ACCURACY, DEFAULT_LOCATION_ACCURACY); + KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy); break; case KEY_MOTION_INACTIVE_TIMEOUT: MOTION_INACTIVE_TIMEOUT = properties.getLong( - KEY_MOTION_INACTIVE_TIMEOUT, DEFAULT_MOTION_INACTIVE_TIMEOUT); + KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout); break; case KEY_MOTION_INACTIVE_TIMEOUT_FLEX: MOTION_INACTIVE_TIMEOUT_FLEX = properties.getLong( KEY_MOTION_INACTIVE_TIMEOUT_FLEX, - DEFAULT_MOTION_INACTIVE_TIMEOUT_FLEX); + mDefaultMotionInactiveTimeoutFlex); break; case KEY_IDLE_AFTER_INACTIVE_TIMEOUT: final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY - : DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT; + : mDefaultIdleAfterInactiveTimeout; IDLE_AFTER_INACTIVE_TIMEOUT = properties.getLong( KEY_IDLE_AFTER_INACTIVE_TIMEOUT, defaultIdleAfterInactiveTimeout); break; case KEY_IDLE_PENDING_TIMEOUT: IDLE_PENDING_TIMEOUT = properties.getLong( - KEY_IDLE_PENDING_TIMEOUT, DEFAULT_IDLE_PENDING_TIMEOUT); + KEY_IDLE_PENDING_TIMEOUT, mDefaultIdlePendingTimeout); break; case KEY_MAX_IDLE_PENDING_TIMEOUT: MAX_IDLE_PENDING_TIMEOUT = properties.getLong( - KEY_MAX_IDLE_PENDING_TIMEOUT, DEFAULT_MAX_IDLE_PENDING_TIMEOUT); + KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout); break; case KEY_IDLE_PENDING_FACTOR: IDLE_PENDING_FACTOR = properties.getFloat( - KEY_IDLE_PENDING_FACTOR, DEFAULT_IDLE_PENDING_FACTOR); + KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor); break; case KEY_QUICK_DOZE_DELAY_TIMEOUT: QUICK_DOZE_DELAY_TIMEOUT = properties.getLong( - KEY_QUICK_DOZE_DELAY_TIMEOUT, DEFAULT_QUICK_DOZE_DELAY_TIMEOUT); + KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout); break; case KEY_IDLE_TIMEOUT: IDLE_TIMEOUT = properties.getLong( - KEY_IDLE_TIMEOUT, DEFAULT_IDLE_TIMEOUT); + KEY_IDLE_TIMEOUT, mDefaultIdleTimeout); break; case KEY_MAX_IDLE_TIMEOUT: MAX_IDLE_TIMEOUT = properties.getLong( - KEY_MAX_IDLE_TIMEOUT, DEFAULT_MAX_IDLE_TIMEOUT); + KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout); break; case KEY_IDLE_FACTOR: - IDLE_FACTOR = properties.getFloat(KEY_IDLE_FACTOR, DEFAULT_IDLE_FACTOR); + IDLE_FACTOR = properties.getFloat(KEY_IDLE_FACTOR, mDefaultIdleFactor); break; case KEY_MIN_TIME_TO_ALARM: MIN_TIME_TO_ALARM = properties.getLong( - KEY_MIN_TIME_TO_ALARM, DEFAULT_MIN_TIME_TO_ALARM); + KEY_MIN_TIME_TO_ALARM, mDefaultMinTimeToAlarm); break; case KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS: MAX_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong( KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS, - DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS); + mDefaultMaxTempAppAllowlistDurationMs); break; case KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS: MMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong( KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS, - DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS); + mDefaultMmsTempAppAllowlistDurationMs); break; case KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS: SMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong( KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS, - DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS); + mDefaultSmsTempAppAllowlistDurationMs); break; case KEY_NOTIFICATION_ALLOWLIST_DURATION_MS: NOTIFICATION_ALLOWLIST_DURATION_MS = properties.getLong( KEY_NOTIFICATION_ALLOWLIST_DURATION_MS, - DEFAULT_NOTIFICATION_ALLOWLIST_DURATION_MS); + mDefaultNotificationAllowlistDurationMs); break; case KEY_WAIT_FOR_UNLOCK: WAIT_FOR_UNLOCK = properties.getBoolean( - KEY_WAIT_FOR_UNLOCK, DEFAULT_WAIT_FOR_UNLOCK); + KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock); break; case KEY_PRE_IDLE_FACTOR_LONG: PRE_IDLE_FACTOR_LONG = properties.getFloat( - KEY_PRE_IDLE_FACTOR_LONG, DEFAULT_PRE_IDLE_FACTOR_LONG); + KEY_PRE_IDLE_FACTOR_LONG, mDefaultPreIdleFactorLong); break; case KEY_PRE_IDLE_FACTOR_SHORT: PRE_IDLE_FACTOR_SHORT = properties.getFloat( - KEY_PRE_IDLE_FACTOR_SHORT, DEFAULT_PRE_IDLE_FACTOR_SHORT); + KEY_PRE_IDLE_FACTOR_SHORT, mDefaultPreIdleFactorShort); break; case KEY_USE_WINDOW_ALARMS: USE_WINDOW_ALARMS = properties.getBoolean( - KEY_USE_WINDOW_ALARMS, DEFAULT_USE_WINDOW_ALARMS); + KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms); break; default: Slog.e(TAG, "Unknown configuration key: " + name); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index b6d29aa5641a..5b61f9abc357 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -676,6 +676,9 @@ public class AlarmManagerService extends SystemService { private static final String KEY_TIME_TICK_ALLOWED_WHILE_IDLE = "time_tick_allowed_while_idle"; + private static final String KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = + "delay_nonwakeup_alarms_while_screen_off"; + @VisibleForTesting static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota"; @@ -743,6 +746,8 @@ public class AlarmManagerService extends SystemService { private static final int DEFAULT_TEMPORARY_QUOTA_BUMP = 0; + private static final boolean DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = true; + // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -835,6 +840,9 @@ public class AlarmManagerService extends SystemService { */ public int TEMPORARY_QUOTA_BUMP = DEFAULT_TEMPORARY_QUOTA_BUMP; + public boolean DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = + DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF; + private long mLastAllowWhileIdleWhitelistDuration = -1; private int mVersion = 0; @@ -1011,6 +1019,11 @@ public class AlarmManagerService extends SystemService { TEMPORARY_QUOTA_BUMP = properties.getInt(KEY_TEMPORARY_QUOTA_BUMP, DEFAULT_TEMPORARY_QUOTA_BUMP); break; + case KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF: + DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = properties.getBoolean( + KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF, + DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -1249,6 +1262,10 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_TEMPORARY_QUOTA_BUMP, TEMPORARY_QUOTA_BUMP); pw.println(); + pw.print(KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF, + DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF); + pw.println(); + pw.decreaseIndent(); } @@ -4360,7 +4377,11 @@ public class AlarmManagerService extends SystemService { } } + @GuardedBy("mLock") boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) { + if (!mConstants.DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF) { + return false; + } if (mInteractive) { return false; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index 3bbc5a369684..d284a99a4559 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -69,6 +69,11 @@ public final class BatteryController extends RestrictingController { */ private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>(); + @GuardedBy("mLock") + private Boolean mLastReportedStatsdBatteryNotLow = null; + @GuardedBy("mLock") + private Boolean mLastReportedStatsdStablePower = null; + public BatteryController(JobSchedulerService service, FlexibilityController flexibilityController) { super(service); @@ -176,12 +181,25 @@ public final class BatteryController extends RestrictingController { Slog.d(TAG, "maybeReportNewChargingStateLocked: " + powerConnected + "/" + stablePower + "/" + batteryNotLow); } + + if (mLastReportedStatsdStablePower == null + || mLastReportedStatsdStablePower != stablePower) { + logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_CHARGING, stablePower); + mLastReportedStatsdStablePower = stablePower; + } + if (mLastReportedStatsdBatteryNotLow == null + || mLastReportedStatsdBatteryNotLow != stablePower) { + logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_BATTERY_NOT_LOW, + batteryNotLow); + mLastReportedStatsdBatteryNotLow = batteryNotLow; + } + final long nowElapsed = sElapsedRealtimeClock.millis(); mFlexibilityController.setConstraintSatisfied( JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging(), nowElapsed); mFlexibilityController.setConstraintSatisfied( - JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow, nowElapsed); + JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow, nowElapsed); for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { final JobStatus ts = mTrackedTasks.valueAt(i); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index f6de109d7ec9..abbe177c5d49 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -153,6 +153,8 @@ public final class DeviceIdleJobsController extends StateController { changed = true; } mDeviceIdleMode = enabled; + logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_DEVICE_NOT_DOZING, + !mDeviceIdleMode); if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode); mDeviceIdleUpdateFunctor.prepare(); if (enabled) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index f1e13dfcab5c..9c167728cff9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -644,7 +644,7 @@ public final class FlexibilityController extends StateController { static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE = FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms"; static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = - FC_CONFIG_PREFIX + "min_alarm_time_flexibility_ms"; + FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms"; static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints"; static final String KEY_MAX_RESCHEDULED_DEADLINE_MS = diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java index dd0621728724..926cfc192cd1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java @@ -96,6 +96,8 @@ public final class IdleController extends RestrictingController implements Idlen @Override public void reportNewIdleState(boolean isIdle) { synchronized (mLock) { + logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_IDLE, isIdle); + final long nowElapsed = sElapsedRealtimeClock.millis(); mFlexibilityController.setConstraintSatisfied( JobStatus.CONSTRAINT_IDLE, isIdle, nowElapsed); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 4320db09aef5..999a3c02b18c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -170,13 +170,12 @@ public final class JobStatus { */ private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER | CONSTRAINT_DEADLINE - | CONSTRAINT_IDLE | CONSTRAINT_PREFETCH | CONSTRAINT_TARE_WEALTH | CONSTRAINT_TIMING_DELAY | CONSTRAINT_WITHIN_QUOTA; - // TODO(b/129954980) + // TODO(b/129954980): ensure this doesn't spam statsd, especially at boot private static final boolean STATS_LOG_ENABLED = false; // No override. @@ -1982,7 +1981,7 @@ public final class JobStatus { } /** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */ - private int getProtoConstraint(int constraint) { + static int getProtoConstraint(int constraint) { switch (constraint) { case CONSTRAINT_BACKGROUND_NOT_RESTRICTED: return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED; @@ -1998,10 +1997,16 @@ public final class JobStatus { return JobServerProtoEnums.CONSTRAINT_DEADLINE; case CONSTRAINT_DEVICE_NOT_DOZING: return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING; + case CONSTRAINT_FLEXIBLE: + return JobServerProtoEnums.CONSTRAINT_FLEXIBILITY; case CONSTRAINT_IDLE: return JobServerProtoEnums.CONSTRAINT_IDLE; + case CONSTRAINT_PREFETCH: + return JobServerProtoEnums.CONSTRAINT_PREFETCH; case CONSTRAINT_STORAGE_NOT_LOW: return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW; + case CONSTRAINT_TARE_WEALTH: + return JobServerProtoEnums.CONSTRAINT_TARE_WEALTH; case CONSTRAINT_TIMING_DELAY: return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY; case CONSTRAINT_WITHIN_QUOTA: diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java index 2a2d602b24bf..8453e53782ca 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java @@ -26,6 +26,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobSchedulerService.Constants; import com.android.server.job.StateChangedListener; @@ -165,6 +166,15 @@ public abstract class StateController { return mService.areComponentsInPlaceLocked(jobStatus); } + protected void logDeviceWideConstraintStateToStatsd(int constraint, boolean satisfied) { + FrameworkStatsLog.write( + FrameworkStatsLog.DEVICE_WIDE_JOB_CONSTRAINT_CHANGED, + JobStatus.getProtoConstraint(constraint), + satisfied + ? FrameworkStatsLog.DEVICE_WIDE_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED + : FrameworkStatsLog.DEVICE_WIDE_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED); + } + public abstract void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate); public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, 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 4ba69570f7e0..7391bcfa8d88 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -55,12 +55,13 @@ public abstract class EconomicPolicy { static final int TYPE_ACTION = 1 << SHIFT_TYPE; static final int TYPE_REWARD = 2 << SHIFT_TYPE; - private static final int SHIFT_POLICY = 29; - static final int MASK_POLICY = 0b1 << SHIFT_POLICY; - static final int POLICY_AM = 0 << SHIFT_POLICY; - static final int POLICY_JS = 1 << SHIFT_POLICY; + private static final int SHIFT_POLICY = 28; + static final int MASK_POLICY = 0b11 << SHIFT_POLICY; + // Reserve 0 for the base/common policy. + static final int POLICY_AM = 1 << SHIFT_POLICY; + static final int POLICY_JS = 2 << SHIFT_POLICY; - static final int MASK_EVENT = ~0 - (0b111 << SHIFT_POLICY); + static final int MASK_EVENT = -1 ^ (MASK_TYPE | MASK_POLICY); static final int REGULATION_BASIC_INCOME = TYPE_REGULATION | 0; static final int REGULATION_BIRTHRIGHT = TYPE_REGULATION | 1; @@ -119,6 +120,7 @@ public abstract class EconomicPolicy { REWARD_NOTIFICATION_INTERACTION, REWARD_WIDGET_INTERACTION, REWARD_OTHER_USER_INTERACTION, + JobSchedulerEconomicPolicy.REWARD_APP_INSTALL, }) @Retention(RetentionPolicy.SOURCE) public @interface UtilityReward { @@ -429,6 +431,8 @@ public abstract class EconomicPolicy { return "REWARD_WIDGET_INTERACTION"; case REWARD_OTHER_USER_INTERACTION: return "REWARD_OTHER_USER_INTERACTION"; + case JobSchedulerEconomicPolicy.REWARD_APP_INSTALL: + return "REWARD_JOB_APP_INSTALL"; } return "UNKNOWN_REWARD:" + Integer.toHexString(eventId); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java index da544bb6a2eb..fcb3e6759412 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java @@ -17,9 +17,12 @@ package com.android.server.tare; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppGlobals; import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; import android.content.pm.PackageInfo; -import android.os.UserHandle; +import android.os.RemoteException; /** POJO to cache only the information about installed packages that TARE cares about. */ class InstalledPackageInfo { @@ -28,11 +31,21 @@ class InstalledPackageInfo { public final int uid; public final String packageName; public final boolean hasCode; + @Nullable + public final String installerPackageName; InstalledPackageInfo(@NonNull PackageInfo packageInfo) { final ApplicationInfo applicationInfo = packageInfo.applicationInfo; - this.uid = applicationInfo == null ? NO_UID : applicationInfo.uid; - this.packageName = packageInfo.packageName; - this.hasCode = applicationInfo != null && applicationInfo.hasCode(); + uid = applicationInfo == null ? NO_UID : applicationInfo.uid; + packageName = packageInfo.packageName; + hasCode = applicationInfo != null && applicationInfo.hasCode(); + InstallSourceInfo installSourceInfo = null; + try { + installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName); + } catch (RemoteException e) { + // Shouldn't happen. + } + installerPackageName = + installSourceInfo == null ? null : installSourceInfo.getInstallingPackageName(); } } 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 59d4ded8b1d2..c13e1dd978d6 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -524,10 +524,15 @@ public class InternalResourceService extends SystemService { mPackageToUidCache.add(userId, pkgName, uid); } synchronized (mLock) { - mPkgCache.add(userId, pkgName, new InstalledPackageInfo(packageInfo)); + final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo); + mPkgCache.add(userId, pkgName, ipo); mUidToPackageCache.add(uid, pkgName); // TODO: only do this when the user first launches the app (app leaves stopped state) mAgent.grantBirthrightLocked(userId, pkgName); + if (ipo.installerPackageName != null) { + mAgent.noteInstantaneousEventLocked(userId, ipo.installerPackageName, + JobSchedulerEconomicPolicy.REWARD_APP_INSTALL, null); + } } } 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 cbb88c0f5e31..55cc3520961e 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -43,6 +43,9 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIM 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_APP_INSTALL_INSTANT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_ONGOING_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; @@ -85,6 +88,9 @@ import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP; +import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_INSTANT; +import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_MAX; +import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_ONGOING; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING; @@ -137,6 +143,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { public static final int ACTION_JOB_MIN_RUNNING = TYPE_ACTION | POLICY_JS | 9; public static final int ACTION_JOB_TIMEOUT = TYPE_ACTION | POLICY_JS | 10; + public static final int REWARD_APP_INSTALL = TYPE_REWARD | POLICY_JS | 0; + private static final int[] COST_MODIFIERS = new int[]{ COST_MODIFIER_CHARGING, COST_MODIFIER_DEVICE_IDLE, @@ -374,6 +382,17 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { getConstantAsCake(mParser, properties, KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX, DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES))); + mRewards.put(REWARD_APP_INSTALL, + new Reward(REWARD_APP_INSTALL, + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_APP_INSTALL_INSTANT, + DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_APP_INSTALL_ONGOING, + DEFAULT_JS_REWARD_APP_INSTALL_ONGOING_CAKES), + getConstantAsCake(mParser, properties, + KEY_JS_REWARD_APP_INSTALL_MAX, + DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES))); } @Override diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index 2cda57dd67e9..8b8d361edbd4 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -18,24 +18,22 @@ #include "com_android_commands_hid_Device.h" -#include <linux/uhid.h> - +#include <android-base/stringprintf.h> +#include <android/looper.h> #include <fcntl.h> #include <inttypes.h> -#include <unistd.h> -#include <cstdio> -#include <cstring> -#include <memory> - -#include <android/looper.h> #include <jni.h> +#include <linux/uhid.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> +#include <unistd.h> -#include <android-base/stringprintf.h> +#include <cstdio> +#include <cstring> +#include <memory> // Log debug messages about the output. static constexpr bool DEBUG_OUTPUT = false; @@ -109,15 +107,15 @@ void DeviceCallback::onDeviceOpen() { void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) { JNIEnv* env = getJNIEnv(); - env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceGetReport, - requestId, reportId); + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceGetReport, requestId, + reportId); checkAndClearException(env, "onDeviceGetReport"); } -void DeviceCallback::onDeviceSetReport(uint8_t rType, - const std::vector<uint8_t>& data) { +void DeviceCallback::onDeviceSetReport(uint32_t id, uint8_t rType, + const std::vector<uint8_t>& data) { JNIEnv* env = getJNIEnv(); - env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceSetReport, rType, + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceSetReport, id, rType, toJbyteArray(env, data).get()); checkAndClearException(env, "onDeviceSetReport"); } @@ -236,6 +234,14 @@ void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY"); } +void Device::sendSetReportReply(uint32_t id, bool success) const { + struct uhid_event ev = {}; + ev.type = UHID_SET_REPORT_REPLY; + ev.u.set_report_reply.id = id; + ev.u.set_report_reply.err = success ? 0 : EIO; + writeEvent(mFd, ev, "UHID_SET_REPORT_REPLY"); +} + int Device::handleEvents(int events) { if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { ALOGE("uhid node was closed or an error occurred. events=0x%x", events); @@ -249,7 +255,6 @@ int Device::handleEvents(int events) { mDeviceCallback->onDeviceError(); return 0; } - switch (ev.type) { case UHID_OPEN: { mDeviceCallback->onDeviceOpen(); @@ -271,7 +276,7 @@ int Device::handleEvents(int events) { ALOGD("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id, set_report.rnum, toString(data).c_str()); } - mDeviceCallback->onDeviceSetReport(set_report.rtype, data); + mDeviceCallback->onDeviceSetReport(set_report.id, set_report.rtype, data); break; } case UHID_OUTPUT: { @@ -347,6 +352,15 @@ static void sendGetFeatureReportReply(JNIEnv* env, jclass /* clazz */, jlong ptr } } +static void sendSetReportReply(JNIEnv*, jclass /* clazz */, jlong ptr, jint id, jboolean success) { + uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); + if (d) { + d->sendSetReportReply(id, success); + } else { + ALOGE("Could not send set report reply, Device* is null!"); + } +} + static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); if (d) { @@ -362,6 +376,7 @@ static JNINativeMethod sMethods[] = { {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)}, {"nativeSendGetFeatureReportReply", "(JI[B)V", reinterpret_cast<void*>(sendGetFeatureReportReply)}, + {"nativeSendSetReportReply", "(JIZ)V", reinterpret_cast<void*>(sendSetReportReply)}, {"nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice)}, }; @@ -376,7 +391,7 @@ int register_com_android_commands_hid_Device(JNIEnv* env) { uhid::gDeviceCallbackClassInfo.onDeviceGetReport = env->GetMethodID(clazz, "onDeviceGetReport", "(II)V"); uhid::gDeviceCallbackClassInfo.onDeviceSetReport = - env->GetMethodID(clazz, "onDeviceSetReport", "(B[B)V"); + env->GetMethodID(clazz, "onDeviceSetReport", "(IB[B)V"); uhid::gDeviceCallbackClassInfo.onDeviceOutput = env->GetMethodID(clazz, "onDeviceOutput", "(B[B)V"); uhid::gDeviceCallbackClassInfo.onDeviceError = diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index d10a9aa3680c..9c6060d837e0 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -14,12 +14,11 @@ * limitations under the License. */ -#include <memory> -#include <vector> - +#include <android-base/unique_fd.h> #include <jni.h> -#include <android-base/unique_fd.h> +#include <memory> +#include <vector> namespace android { namespace uhid { @@ -31,7 +30,7 @@ public: void onDeviceOpen(); void onDeviceGetReport(uint32_t requestId, uint8_t reportId); - void onDeviceSetReport(uint8_t rType, const std::vector<uint8_t>& data); + void onDeviceSetReport(uint32_t id, uint8_t rType, const std::vector<uint8_t>& data); void onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data); void onDeviceError(); @@ -50,9 +49,9 @@ public: ~Device(); void sendReport(const std::vector<uint8_t>& report) const; + void sendSetReportReply(uint32_t id, bool success) const; void sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const; void close(); - int handleEvents(int events); private: diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 95b1e9a7b57f..0415037cfc9f 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -42,7 +42,8 @@ public class Device { private static final int MSG_OPEN_DEVICE = 1; private static final int MSG_SEND_REPORT = 2; private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3; - private static final int MSG_CLOSE_DEVICE = 4; + private static final int MSG_SEND_SET_REPORT_REPLY = 4; + private static final int MSG_CLOSE_DEVICE = 5; // Sync with linux uhid_event_type::UHID_OUTPUT private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6; @@ -56,21 +57,45 @@ public class Device { private final Map<ByteBuffer, byte[]> mOutputs; private final OutputStream mOutputStream; private long mTimeToSend; - private final Object mCond = new Object(); + /** + * The report id of the report received in UHID_EVENT_TYPE_SET_REPORT. + * Used for SET_REPORT_REPLY. + * This field gets overridden each time SET_REPORT is received. + */ + private int mResponseId; static { System.loadLibrary("hidcommand_jni"); } - private static native long nativeOpenDevice(String name, int id, int vid, int pid, int bus, - byte[] descriptor, DeviceCallback callback); + private static native long nativeOpenDevice( + String name, + int id, + int vid, + int pid, + int bus, + byte[] descriptor, + DeviceCallback callback); + private static native void nativeSendReport(long ptr, byte[] data); + private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data); + + private static native void nativeSendSetReportReply(long ptr, int id, boolean success); + private static native void nativeCloseDevice(long ptr); - public Device(int id, String name, int vid, int pid, int bus, byte[] descriptor, - byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) { + public Device( + int id, + String name, + int vid, + int pid, + int bus, + byte[] descriptor, + byte[] report, + SparseArray<byte[]> featureReports, + Map<ByteBuffer, byte[]> outputs) { mId = id; mThread = new HandlerThread("HidDeviceHandler"); mThread.start(); @@ -100,6 +125,17 @@ public class Device { mHandler.sendMessageAtTime(msg, mTimeToSend); } + public void setGetReportResponse(byte[] report) { + mFeatureReports.put(report[0], report); + } + + public void sendSetReportReply(boolean success) { + Message msg = + mHandler.obtainMessage(MSG_SEND_SET_REPORT_REPLY, mResponseId, success ? 1 : 0); + + mHandler.sendMessageAtTime(msg, mTimeToSend); + } + public void addDelay(int delay) { mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; } @@ -111,7 +147,8 @@ public class Device { synchronized (mCond) { mCond.wait(); } - } catch (InterruptedException ignore) {} + } catch (InterruptedException ignore) { + } } private class DeviceHandler extends Handler { @@ -127,8 +164,15 @@ public class Device { switch (msg.what) { case MSG_OPEN_DEVICE: SomeArgs args = (SomeArgs) msg.obj; - mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3, - args.argi4, (byte[]) args.arg2, new DeviceCallback()); + mPtr = + nativeOpenDevice( + (String) args.arg1, + args.argi1, + args.argi2, + args.argi3, + args.argi4, + (byte[]) args.arg2, + new DeviceCallback()); pauseEvents(); break; case MSG_SEND_REPORT: @@ -145,6 +189,14 @@ public class Device { Log.e(TAG, "Tried to send feature report reply to closed device."); } break; + case MSG_SEND_SET_REPORT_REPLY: + if (mPtr != 0) { + final boolean success = msg.arg2 == 1; + nativeSendSetReportReply(mPtr, msg.arg1, success); + } else { + Log.e(TAG, "Tried to send set report reply to closed device."); + } + break; case MSG_CLOSE_DEVICE: if (mPtr != 0) { nativeCloseDevice(mPtr); @@ -173,14 +225,18 @@ public class Device { } private class DeviceCallback { + public void onDeviceOpen() { mHandler.resumeEvents(); } public void onDeviceGetReport(int requestId, int reportId) { if (mFeatureReports == null) { - Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId - + ", but 'feature_reports' section is not found"); + Log.e( + TAG, + "Received GET_REPORT request for reportId=" + + reportId + + ", but 'feature_reports' section is not found"); return; } byte[] report = mFeatureReports.get(reportId); @@ -220,14 +276,15 @@ public class Device { } catch (IOException e) { throw new RuntimeException(e); } - } // native callback - public void onDeviceSetReport(byte rtype, byte[] data) { + public void onDeviceSetReport(int id, byte rType, byte[] data) { + // Used by sendSetReportReply() + mResponseId = id; // We don't need to reply for the SET_REPORT but just send it to HID output for test // verification. - sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rtype, data); + sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rType, data); } // native callback @@ -239,7 +296,8 @@ public class Device { } byte[] response = mOutputs.get(ByteBuffer.wrap(data)); if (response == null) { - Log.i(TAG, + Log.i( + TAG, "Requested response for output " + Arrays.toString(data) + " is not found"); return; } diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java index d4bf1d820a70..3efb79766b94 100644 --- a/cmds/hid/src/com/android/commands/hid/Event.java +++ b/cmds/hid/src/com/android/commands/hid/Event.java @@ -35,6 +35,8 @@ public class Event { public static final String COMMAND_REGISTER = "register"; public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_REPORT = "report"; + public static final String COMMAND_SET_GET_REPORT_RESPONSE = "set_get_report_response"; + public static final String COMMAND_SEND_SET_REPORT_REPLY = "send_set_report_reply"; // These constants come from "include/uapi/linux/input.h" in the kernel enum Bus { @@ -62,6 +64,7 @@ public class Event { private SparseArray<byte[]> mFeatureReports; private Map<ByteBuffer, byte[]> mOutputs; private int mDuration; + private Boolean mReply; public int getId() { return mId; @@ -107,6 +110,10 @@ public class Event { return mDuration; } + public Boolean getReply() { + return mReply; + } + public String toString() { return "Event{id=" + mId + ", command=" + String.valueOf(mCommand) @@ -119,6 +126,7 @@ public class Event { + ", feature_reports=" + mFeatureReports.toString() + ", outputs=" + mOutputs.toString() + ", duration=" + mDuration + + ", success=" + mReply.toString() + "}"; } @@ -173,6 +181,10 @@ public class Event { mEvent.mDuration = duration; } + public void setReply(boolean success) { + mEvent.mReply = success; + } + public Event build() { if (mEvent.mId == -1) { throw new IllegalStateException("No event id"); @@ -183,6 +195,16 @@ public class Event { if (mEvent.mDescriptor == null) { throw new IllegalStateException("Device registration is missing descriptor"); } + } + if (COMMAND_SET_GET_REPORT_RESPONSE.equals(mEvent.mCommand)) { + if (mEvent.mReport == null) { + throw new IllegalStateException("Report command is missing response data"); + } + } + if (COMMAND_SEND_SET_REPORT_REPLY.equals(mEvent.mCommand)) { + if (mEvent.mReply == null) { + throw new IllegalStateException("Reply command is missing reply"); + } } else if (COMMAND_DELAY.equals(mEvent.mCommand)) { if (mEvent.mDuration <= 0) { throw new IllegalStateException("Delay has missing or invalid duration"); @@ -246,6 +268,9 @@ public class Event { case "duration": eb.setDuration(readInt()); break; + case "success": + eb.setReply(readBool()); + break; default: mReader.skipValue(); } @@ -292,6 +317,11 @@ public class Event { return Integer.decode(val); } + private boolean readBool() throws IOException { + String val = mReader.nextString(); + return Boolean.parseBoolean(val); + } + private Bus readBus() throws IOException { String val = mReader.nextString(); return Bus.valueOf(val.toUpperCase()); diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java index fac0ab2ef125..2db791fe90bd 100644 --- a/cmds/hid/src/com/android/commands/hid/Hid.java +++ b/cmds/hid/src/com/android/commands/hid/Hid.java @@ -93,6 +93,10 @@ public class Hid { d.addDelay(e.getDuration()); } else if (Event.COMMAND_REPORT.equals(e.getCommand())) { d.sendReport(e.getReport()); + } else if (Event.COMMAND_SET_GET_REPORT_RESPONSE.equals(e.getCommand())) { + d.setGetReportResponse(e.getReport()); + } else if (Event.COMMAND_SEND_SET_REPORT_REPLY.equals(e.getCommand())) { + d.sendSetReportReply(e.getReply()); } else { if (Event.COMMAND_REGISTER.equals(e.getCommand())) { error("Device id=" + e.getId() + " is already registered. Ignoring event."); diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index 58aff42b1e98..03e714a3847e 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -21,42 +21,51 @@ * header := magic version target_crc overlay_crc fulfilled_policies * enforce_overlayable target_path overlay_path overlay_name * debug_info - * data := data_header target_entry* target_inline_entry* overlay_entry* - * string_pool - * data_header := target_entry_count target_inline_entry_count overlay_entry_count + * data := data_header target_entry* target_inline_entry* + target_inline_entry_value* config* overlay_entry* string_pool + * data_header := target_entry_count target_inline_entry_count + target_inline_entry_value_count config_count overlay_entry_count * string_pool_index * target_entry := target_id overlay_id - * target_inline_entry := target_id Res_value::size padding(1) Res_value::type + * target_inline_entry := target_id start_value_index value_count + * target_inline_entry_value := config_index Res_value::size padding(1) Res_value::type + * Res_value::value + * config := target_id Res_value::size padding(1) Res_value::type * Res_value::value * overlay_entry := overlay_id target_id * - * debug_info := string - * enforce_overlayable := <uint32_t> - * fulfilled_policies := <uint32_t> - * magic := <uint32_t> - * overlay_crc := <uint32_t> - * overlay_entry_count := <uint32_t> - * overlay_id := <uint32_t> - * overlay_package_id := <uint8_t> - * overlay_name := string - * overlay_path := string - * padding(n) := <uint8_t>[n] - * Res_value::size := <uint16_t> - * Res_value::type := <uint8_t> - * Res_value::value := <uint32_t> - * string := <uint32_t> <uint8_t>+ padding(n) - * string_pool := string - * string_pool_index := <uint32_t> - * string_pool_length := <uint32_t> - * target_crc := <uint32_t> - * target_entry_count := <uint32_t> - * target_inline_entry_count := <uint32_t> - * target_id := <uint32_t> - * target_package_id := <uint8_t> - * target_path := string - * value_type := <uint8_t> - * value_data := <uint32_t> - * version := <uint32_t> + * debug_info := string + * enforce_overlayable := <uint32_t> + * fulfilled_policies := <uint32_t> + * magic := <uint32_t> + * overlay_crc := <uint32_t> + * overlay_entry_count := <uint32_t> + * overlay_id := <uint32_t> + * overlay_package_id := <uint8_t> + * overlay_name := string + * overlay_path := string + * padding(n) := <uint8_t>[n] + * Res_value::size := <uint16_t> + * Res_value::type := <uint8_t> + * Res_value::value := <uint32_t> + * string := <uint32_t> <uint8_t>+ padding(n) + * string_pool := string + * string_pool_index := <uint32_t> + * string_pool_length := <uint32_t> + * target_crc := <uint32_t> + * target_entry_count := <uint32_t> + * target_inline_entry_count := <uint32_t> + * target_inline_entry_value_count := <uint32_t> + * config_count := <uint32_t> + * config_index := <uint32_t> + * start_value_index := <uint32_t> + * value_count := <uint32_t> + * target_id := <uint32_t> + * target_package_id := <uint8_t> + * target_path := string + * value_type := <uint8_t> + * value_data := <uint32_t> + * version := <uint32_t> */ #ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ @@ -70,6 +79,7 @@ #include "android-base/macros.h" #include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" +#include "androidfw/ConfigDescription.h" #include "idmap2/ResourceContainer.h" #include "idmap2/ResourceMapping.h" @@ -165,19 +175,27 @@ class IdmapData { public: static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream); - inline uint32_t GetTargetEntryCount() const { + [[nodiscard]] inline uint32_t GetTargetEntryCount() const { return target_entry_count; } - inline uint32_t GetTargetInlineEntryCount() const { + [[nodiscard]] inline uint32_t GetTargetInlineEntryCount() const { return target_entry_inline_count; } - inline uint32_t GetOverlayEntryCount() const { + [[nodiscard]] inline uint32_t GetTargetInlineEntryValueCount() const { + return target_entry_inline_value_count; + } + + [[nodiscard]] inline uint32_t GetConfigCount() const { + return config_count; + } + + [[nodiscard]] inline uint32_t GetOverlayEntryCount() const { return overlay_entry_count; } - inline uint32_t GetStringPoolIndexOffset() const { + [[nodiscard]] inline uint32_t GetStringPoolIndexOffset() const { return string_pool_index_offset; } @@ -186,6 +204,8 @@ class IdmapData { private: uint32_t target_entry_count; uint32_t target_entry_inline_count; + uint32_t target_entry_inline_value_count; + uint32_t config_count; uint32_t overlay_entry_count; uint32_t string_pool_index_offset; Header() = default; @@ -202,7 +222,7 @@ class IdmapData { struct TargetInlineEntry { ResourceId target_id; - TargetValue value; + std::map<ConfigDescription, TargetValue> values; }; struct OverlayEntry { @@ -227,11 +247,11 @@ class IdmapData { return target_inline_entries_; } - const std::vector<OverlayEntry>& GetOverlayEntries() const { + [[nodiscard]] const std::vector<OverlayEntry>& GetOverlayEntries() const { return overlay_entries_; } - const std::string& GetStringPoolData() const { + [[nodiscard]] const std::string& GetStringPoolData() const { return string_pool_data_; } diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h index 21862a3635d5..4bad2fa392a2 100644 --- a/cmds/idmap2/include/idmap2/ResourceMapping.h +++ b/cmds/idmap2/include/idmap2/ResourceMapping.h @@ -33,7 +33,8 @@ using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; namespace android::idmap2 { -using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, TargetValue>>; +using ConfigMap = std::unordered_map<std::string, TargetValue>; +using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, ConfigMap>>; using OverlayResourceMap = std::map<ResourceId, ResourceId>; class ResourceMapping { diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index 8ec749602c4a..af4dd8960cc3 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -46,6 +46,10 @@ struct TargetValue { struct TargetValueWithConfig { TargetValue value; std::string config; + + [[nodiscard]] std::pair<std::string, TargetValue> to_pair() const { + return std::make_pair(config, value); + } }; namespace utils { diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp index 3bbe9d91deb6..4b271a1ff96f 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -70,12 +70,35 @@ void BinaryStreamVisitor::visit(const IdmapData& data) { } static constexpr uint16_t kValueSize = 8U; + std::vector<std::pair<ConfigDescription, TargetValue>> target_values; + target_values.reserve(data.GetHeader()->GetTargetInlineEntryValueCount()); for (const auto& target_entry : data.GetTargetInlineEntries()) { Write32(target_entry.target_id); + Write32(target_values.size()); + Write32(target_entry.values.size()); + target_values.insert( + target_values.end(), target_entry.values.begin(), target_entry.values.end()); + } + + std::vector<ConfigDescription> configs; + configs.reserve(data.GetHeader()->GetConfigCount()); + for (const auto& target_entry_value : target_values) { + auto config_it = find(configs.begin(), configs.end(), target_entry_value.first); + if (config_it != configs.end()) { + Write32(config_it - configs.begin()); + } else { + Write32(configs.size()); + configs.push_back(target_entry_value.first); + } Write16(kValueSize); Write8(0U); // padding - Write8(target_entry.value.data_type); - Write32(target_entry.value.data_value); + Write8(target_entry_value.second.data_type); + Write32(target_entry_value.second.data_value); + } + + for( auto& cd : configs) { + cd.swapHtoD(); + stream_.write(reinterpret_cast<char*>(&cd), sizeof(cd)); } for (const auto& overlay_entry : data.GetOverlayEntries()) { @@ -89,6 +112,8 @@ void BinaryStreamVisitor::visit(const IdmapData& data) { void BinaryStreamVisitor::visit(const IdmapData::Header& header) { Write32(header.GetTargetEntryCount()); Write32(header.GetTargetInlineEntryCount()); + Write32(header.GetTargetInlineEntryValueCount()); + Write32(header.GetConfigCount()); Write32(header.GetOverlayEntryCount()); Write32(header.GetStringPoolIndexOffset()); } diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 06650f681b24..4efaeea74274 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -186,6 +186,8 @@ std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header()); if (!Read32(stream, &idmap_data_header->target_entry_count) || !Read32(stream, &idmap_data_header->target_entry_inline_count) || + !Read32(stream, &idmap_data_header->target_entry_inline_value_count) || + !Read32(stream, &idmap_data_header->config_count) || !Read32(stream, &idmap_data_header->overlay_entry_count) || !Read32(stream, &idmap_data_header->string_pool_index_offset)) { return nullptr; @@ -207,20 +209,59 @@ std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& strea if (!Read32(stream, &target_entry.target_id) || !Read32(stream, &target_entry.overlay_id)) { return nullptr; } - data->target_entries_.push_back(target_entry); + data->target_entries_.emplace_back(target_entry); } // Read the mapping of target resource id to inline overlay values. - uint8_t unused1; - uint16_t unused2; + std::vector<std::tuple<TargetInlineEntry, uint32_t, uint32_t>> target_inline_entries; for (size_t i = 0; i < data->header_->GetTargetInlineEntryCount(); i++) { TargetInlineEntry target_entry{}; - if (!Read32(stream, &target_entry.target_id) || !Read16(stream, &unused2) || - !Read8(stream, &unused1) || !Read8(stream, &target_entry.value.data_type) || - !Read32(stream, &target_entry.value.data_value)) { + uint32_t entry_offset; + uint32_t entry_count; + if (!Read32(stream, &target_entry.target_id) || !Read32(stream, &entry_offset) + || !Read32(stream, &entry_count)) { return nullptr; } - data->target_inline_entries_.push_back(target_entry); + target_inline_entries.emplace_back(std::make_tuple(target_entry, entry_offset, entry_count)); + } + + // Read the inline overlay resource values + std::vector<std::pair<uint32_t, TargetValue>> target_values; + uint8_t unused1; + uint16_t unused2; + for (size_t i = 0; i < data->header_->GetTargetInlineEntryValueCount(); i++) { + uint32_t config_index; + if (!Read32(stream, &config_index)) { + return nullptr; + } + TargetValue value; + if (!Read16(stream, &unused2) + || !Read8(stream, &unused1) + || !Read8(stream, &value.data_type) + || !Read32(stream, &value.data_value)) { + return nullptr; + } + target_values.emplace_back(std::make_pair(config_index, value)); + } + + // Read the configurations + std::vector<ConfigDescription> configurations; + for (size_t i = 0; i < data->header_->GetConfigCount(); i++) { + ConfigDescription cd; + if (!stream.read(reinterpret_cast<char*>(&cd), sizeof(ConfigDescription))) { + return nullptr; + } + configurations.emplace_back(cd); + } + + // Construct complete target inline entries + for (auto [target_entry, entry_offset, entry_count] : target_inline_entries) { + for(size_t i = 0; i < entry_count; i++) { + const auto& target_value = target_values[entry_offset + i]; + const auto& config = configurations[target_value.first]; + target_entry.values[config] = target_value.second; + } + data->target_inline_entries_.emplace_back(target_entry); } // Read the mapping of overlay resource id to target resource id. @@ -278,12 +319,21 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( std::unique_ptr<IdmapData> data(new IdmapData()); data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string(); + uint32_t inline_value_count = 0; + std::set<std::string> config_set; for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) { if (auto overlay_resource = std::get_if<ResourceId>(&mapping.second)) { data->target_entries_.push_back({mapping.first, *overlay_resource}); } else { - data->target_inline_entries_.push_back( - {mapping.first, std::get<TargetValue>(mapping.second)}); + std::map<ConfigDescription, TargetValue> values; + for (const auto& [config, value] : std::get<ConfigMap>(mapping.second)) { + config_set.insert(config); + ConfigDescription cd; + ConfigDescription::Parse(config, &cd); + values[cd] = value; + inline_value_count++; + } + data->target_inline_entries_.push_back({mapping.first, values}); } } @@ -295,6 +345,8 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size()); data_header->target_entry_inline_count = static_cast<uint32_t>(data->target_inline_entries_.size()); + data_header->target_entry_inline_value_count = inline_value_count; + data_header->config_count = config_set.size(); data_header->overlay_entry_count = static_cast<uint32_t>(data->overlay_entries_.size()); data_header->string_pool_index_offset = resource_mapping.GetStringPoolOffset(); data->header_ = std::move(data_header); diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp index d10a2785aaba..a44fa756aa1c 100644 --- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -94,14 +94,17 @@ void PrettyPrintVisitor::visit(const IdmapData& data) { } for (auto& target_entry : data.GetTargetInlineEntries()) { - stream_ << TAB << base::StringPrintf("0x%08x -> ", target_entry.target_id) - << utils::DataTypeToString(target_entry.value.data_type); - - if (target_entry.value.data_type == Res_value::TYPE_STRING) { - auto str = string_pool.stringAt(target_entry.value.data_value - string_pool_offset); - stream_ << " \"" << str.value_or(StringPiece16(u"")) << "\""; - } else { - stream_ << " " << base::StringPrintf("0x%08x", target_entry.value.data_value); + for(auto iter = target_entry.values.begin(); iter != target_entry.values.end(); ++iter) { + auto value = iter->second; + stream_ << TAB << base::StringPrintf("0x%08x -> ", target_entry.target_id) + << utils::DataTypeToString(value.data_type); + + if (value.data_type == Res_value::TYPE_STRING) { + auto str = string_pool.stringAt(value.data_value - string_pool_offset); + stream_ << " \"" << str.value_or(StringPiece16(u"")) << "\""; + } else { + stream_ << " " << base::StringPrintf("0x%08x", value.data_value); + } } std::string target_name = kUnknownResourceName; diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index 779538c617f4..3531cd7c2f36 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -89,22 +89,30 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { print(target_entry.target_id, "target id"); } + pad(sizeof(Res_value::size) + sizeof(Res_value::res0)); - print(target_entry.value.data_type, "type: %s", - utils::DataTypeToString(target_entry.value.data_type).data()); + for (auto& target_entry_value : target_entry.values) { + auto value = target_entry_value.second; - Result<std::string> overlay_name(Error("")); - if (overlay_ != nullptr && - (target_entry.value.data_value == Res_value::TYPE_REFERENCE || - target_entry.value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) { - overlay_name = overlay_->GetResourceName(target_entry.value.data_value); - } + print(target_entry_value.first.to_string(), false, "config: %s", + target_entry_value.first.toString().string()); - if (overlay_name) { - print(target_entry.value.data_value, "data: %s", overlay_name->c_str()); - } else { - print(target_entry.value.data_value, "data"); + print(value.data_type, "type: %s", + utils::DataTypeToString(value.data_type).data()); + + Result<std::string> overlay_name(Error("")); + if (overlay_ != nullptr && + (value.data_value == Res_value::TYPE_REFERENCE || + value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) { + overlay_name = overlay_->GetResourceName(value.data_value); + } + + if (overlay_name) { + print(value.data_value, "data: %s", overlay_name->c_str()); + } else { + print(value.data_value, "data"); + } } } @@ -138,6 +146,8 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { void RawPrintVisitor::visit(const IdmapData::Header& header) { print(header.GetTargetEntryCount(), "target entry count"); print(header.GetTargetInlineEntryCount(), "target inline entry count"); + print(header.GetTargetInlineEntryValueCount(), "target inline entry value count"); + print(header.GetConfigCount(), "config count"); print(header.GetOverlayEntryCount(), "overlay entry count"); print(header.GetStringPoolIndexOffset(), "string pool index offset"); } diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 8ebe5aa46e73..bb31c11d629c 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -161,14 +161,13 @@ Result<ResourceMapping> ResourceMapping::FromContainers(const TargetResourceCont Result<Unit> ResourceMapping::AddMapping( ResourceId target_resource, const std::variant<OverlayData::ResourceIdValue, TargetValueWithConfig>& value) { - if (target_map_.find(target_resource) != target_map_.end()) { - return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource); - } - // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the // runtime types are not compatible, it could cause runtime crashes when the resource is resolved. if (auto overlay_resource = std::get_if<OverlayData::ResourceIdValue>(&value)) { + if (target_map_.find(target_resource) != target_map_.end()) { + return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource); + } target_map_.insert(std::make_pair(target_resource, overlay_resource->overlay_id)); if (overlay_resource->rewrite_id) { // An overlay resource can override multiple target resources at once. Rewrite the overlay @@ -176,8 +175,18 @@ Result<Unit> ResourceMapping::AddMapping( overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource)); } } else { - auto overlay_value = std::get<TargetValueWithConfig>(value); - target_map_.insert(std::make_pair(target_resource, overlay_value.value)); + auto[iter, inserted] = target_map_.try_emplace(target_resource, ConfigMap()); + auto& resource_value = iter->second; + if (!inserted && std::holds_alternative<ResourceId>(resource_value)) { + return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource); + } + auto& config_map = std::get<ConfigMap>(resource_value); + const auto& config_value = std::get<TargetValueWithConfig>(value); + if (config_map.find(config_value.config) != config_map.end()) { + return Error(R"(target resource id "0x%08x" mapped to multiple values with the same config)", + target_resource); + } + config_map.insert(config_value.to_pair()); } return Unit{}; diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp index bf6332742787..f1eeab9c803b 100644 --- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -84,8 +84,10 @@ TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { const auto& target_inline_entries2 = data2->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries1.size(), target_inline_entries2.size()); ASSERT_EQ(target_inline_entries1[0].target_id, target_inline_entries2[0].target_id); - ASSERT_EQ(target_inline_entries1[0].value.data_type, target_inline_entries2[0].value.data_type); - ASSERT_EQ(target_inline_entries1[0].value.data_value, target_inline_entries2[0].value.data_value); + ASSERT_EQ(target_inline_entries1[0].values.begin()->second.data_type, + target_inline_entries2[0].values.begin()->second.data_type); + ASSERT_EQ(target_inline_entries1[0].values.begin()->second.data_value, + target_inline_entries2[0].values.begin()->second.data_value); const auto& overlay_entries1 = data1->GetOverlayEntries(); const auto& overlay_entries2 = data2->GetOverlayEntries(); diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index ee9a424b3050..7b7dc17deea4 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -47,10 +47,11 @@ namespace android::idmap2 { ASSERT_EQ((entry).target_id, (target_resid)); \ ASSERT_EQ((entry).overlay_id, (overlay_resid)) -#define ASSERT_TARGET_INLINE_ENTRY(entry, target_resid, expected_type, expected_value) \ - ASSERT_EQ((entry).target_id, target_resid); \ - ASSERT_EQ((entry).value.data_type, (expected_type)); \ - ASSERT_EQ((entry).value.data_value, (expected_value)) +#define ASSERT_TARGET_INLINE_ENTRY(entry, target_resid, ex_config, expected_type, expected_value) \ + ASSERT_EQ((entry).target_id, target_resid); \ + ASSERT_EQ((entry).values.begin()->first.to_string(), (ex_config)); \ + ASSERT_EQ((entry).values.begin()->second.data_type, (expected_type)); \ + ASSERT_EQ((entry).values.begin()->second.data_value, (expected_value)) #define ASSERT_OVERLAY_ENTRY(entry, overlay_resid, target_resid) \ ASSERT_EQ((entry).overlay_id, (overlay_resid)); \ @@ -67,7 +68,7 @@ TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); ASSERT_EQ(header->GetMagic(), 0x504d4449U); - ASSERT_EQ(header->GetVersion(), 0x08U); + ASSERT_EQ(header->GetVersion(), 0x09U); ASSERT_EQ(header->GetTargetCrc(), 0x1234U); ASSERT_EQ(header->GetOverlayCrc(), 0x5678U); ASSERT_EQ(header->GetFulfilledPolicies(), 0x11); @@ -122,8 +123,8 @@ TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { const auto& target_inline_entries = data->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries.size(), 1U); - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, Res_value::TYPE_INT_HEX, - 0x12345678); + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, "land-xxhdpi-v7", + Res_value::TYPE_INT_HEX, 0x12345678); const auto& overlay_entries = data->GetOverlayEntries(); ASSERT_EQ(target_entries.size(), 3U); @@ -142,7 +143,7 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x09U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U); ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies); @@ -165,8 +166,8 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) { const auto& target_inline_entries = data->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries.size(), 1U); - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, Res_value::TYPE_INT_HEX, - 0x12345678); + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, "land-xxhdpi-v7", + Res_value::TYPE_INT_HEX, 0x12345678); const auto& overlay_entries = data->GetOverlayEntries(); ASSERT_EQ(target_entries.size(), 3U); @@ -203,7 +204,7 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x09U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC); ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC); @@ -261,9 +262,9 @@ TEST(IdmapTests, FabricatedOverlay) { auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target") .SetOverlayable("TestResources") - .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "") - .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "") - .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "") + .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7") + .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land") + .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7") .Build(); ASSERT_TRUE(frro); @@ -295,11 +296,11 @@ TEST(IdmapTests, FabricatedOverlay) { const auto& target_inline_entries = data->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries.size(), 3U); - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7", Res_value::TYPE_INT_DEC, 2U); - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land", Res_value::TYPE_REFERENCE, 0x7f010000); - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7", Res_value::TYPE_STRING, (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)); } @@ -442,9 +443,9 @@ TEST(IdmapTests, CreateIdmapDataInlineResources) { constexpr size_t overlay_string_pool_size = 10U; const auto& target_inline_entries = data->GetTargetInlineEntries(); ASSERT_EQ(target_inline_entries.size(), 2U); - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, std::string(), Res_value::TYPE_INT_DEC, 73U); // -> 73 - ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, + ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, std::string(), Res_value::TYPE_STRING, overlay_string_pool_size + 0U); // -> "Hello World" diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp index a6371cb74f2e..7112eeb9ea0c 100644 --- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -64,7 +64,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { (*idmap)->accept(&visitor); ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000009 version\n", stream.str()); ASSERT_CONTAINS_REGEX( StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING), stream.str()); @@ -75,6 +75,8 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { ASSERT_CONTAINS_REGEX(ADDRESS "00000001 enforce overlayable\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry count", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry value count", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 config count", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "0000000a string pool index offset", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str()); @@ -111,7 +113,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { (*idmap)->accept(&visitor); ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000009 version\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str()); @@ -124,17 +126,25 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay name: OverlayName\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000001 target inline entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000001 target inline entry value count", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000001 config count", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000003 overlay entry count\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000000 string pool index offset\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f030000 target id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f030000 overlay id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f030001 overlay id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f040000 target id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000e config: land-xxhdpi-v7 size\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "........ config: land-xxhdpi-v7\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 11 type: integer\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "12345678 data\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000004 string pool size\n", stream.str()); - ASSERT_CONTAINS_REGEX("000000a4: ........ string pool\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "........ string pool\n", stream.str()); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp index ca9a444bcb05..016d427e7452 100644 --- a/cmds/idmap2/tests/ResourceMappingTests.cpp +++ b/cmds/idmap2/tests/ResourceMappingTests.cpp @@ -111,19 +111,20 @@ Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& tar return Error("Failed to find mapping for target resource"); } - auto actual_overlay_value = std::get_if<TargetValue>(&entry_map->second); - if (actual_overlay_value == nullptr) { + auto config_map = std::get_if<ConfigMap>(&entry_map->second); + if (config_map == nullptr || config_map->empty()) { return Error("Target resource is not mapped to an inline value"); } + auto actual_overlay_value = config_map->begin()->second; - if (actual_overlay_value->data_type != type) { + if (actual_overlay_value.data_type != type) { return Error(R"(Expected type: "0x%02x" Actual type: "0x%02x")", type, - actual_overlay_value->data_type); + actual_overlay_value.data_type); } - if (actual_overlay_value->data_value != value) { + if (actual_overlay_value.data_value != value) { return Error(R"(Expected value: "0x%08x" Actual value: "0x%08x")", type, - actual_overlay_value->data_value); + actual_overlay_value.data_value); } return Result<Unit>({}); diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h index 6b5f3a8a98eb..45740e2d5a9e 100644 --- a/cmds/idmap2/tests/TestHelpers.h +++ b/cmds/idmap2/tests/TestHelpers.h @@ -30,7 +30,7 @@ const unsigned char kIdmapRawData[] = { 0x49, 0x44, 0x4d, 0x50, // 0x4: version - 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, // 0x8: target crc 0x34, 0x12, 0x00, 0x00, @@ -76,66 +76,123 @@ const unsigned char kIdmapRawData[] = { // 0x58: target_inline_entry_count 0x01, 0x00, 0x00, 0x00, - // 0x5c: overlay_entry_count + // 0x5c: target_inline_entry_value_count + 0x01, 0x00, 0x00, 0x00, + + // 0x60: config_count + 0x01, 0x00, 0x00, 0x00, + + // 0x64: overlay_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x60: string_pool_offset + // 0x68: string_pool_offset 0x00, 0x00, 0x00, 0x00, // TARGET ENTRIES - // 0x64: target id (0x7f020000) + // 0x6c: target id (0x7f020000) 0x00, 0x00, 0x02, 0x7f, - // 0x68: overlay_id (0x7f020000) + // 0x70: overlay_id (0x7f020000) 0x00, 0x00, 0x02, 0x7f, - // 0x6c: target id (0x7f030000) + // 0x74: target id (0x7f030000) 0x00, 0x00, 0x03, 0x7f, - // 0x70: overlay_id (0x7f030000) + // 0x78: overlay_id (0x7f030000) 0x00, 0x00, 0x03, 0x7f, - // 0x74: target id (0x7f030002) + // 0x7c: target id (0x7f030002) 0x02, 0x00, 0x03, 0x7f, - // 0x78: overlay_id (0x7f030001) + // 0x80: overlay_id (0x7f030001) 0x01, 0x00, 0x03, 0x7f, // INLINE TARGET ENTRIES - // 0x7c: target_id + // 0x84: target_id 0x00, 0x00, 0x04, 0x7f, - // 0x80: Res_value::size (value ignored by idmap) + // 0x88: start value index + 0x00, 0x00, 0x00, 0x00, + + // 0x8c: value count + 0x01, 0x00, 0x00, 0x00, + + // INLINE TARGET ENTRY VALUES + + // 0x90: config index + 0x00, 0x00, 0x00, 0x00, + + // 0x94: Res_value::size (value ignored by idmap) 0x08, 0x00, - // 0x82: Res_value::res0 (value ignored by idmap) + // 0x98: Res_value::res0 (value ignored by idmap) 0x00, - // 0x83: Res_value::dataType (TYPE_INT_HEX) + // 0x9c: Res_value::dataType (TYPE_INT_HEX) 0x11, - // 0x84: Res_value::data + // 0xa0: Res_value::data 0x78, 0x56, 0x34, 0x12, + // CONFIGURATIONS + + // 0xa4: ConfigDescription + // size + 0x40, 0x00, 0x00, 0x00, + // 0xa8: imsi + 0x00, 0x00, 0x00, 0x00, + // 0xac: locale + 0x00, 0x00, 0x00, 0x00, + // 0xb0: screenType + 0x02, 0x00, 0xe0, 0x01, + // 0xb4: input + 0x00, 0x00, 0x00, 0x00, + // 0xb8: screenSize + 0x00, 0x00, 0x00, 0x00, + // 0xbc: version + 0x07, 0x00, 0x00, 0x00, + // 0xc0: screenConfig + 0x00, 0x00, 0x00, 0x00, + // 0xc4: screenSizeDp + 0x00, 0x00, 0x00, 0x00, + // 0xc8: localeScript + 0x00, 0x00, 0x00, 0x00, + // 0xcc: localVariant(1) + 0x00, 0x00, 0x00, 0x00, + // 0xd0: localVariant(2) + 0x00, 0x00, 0x00, 0x00, + // 0xd4: screenConfig2 + 0x00, 0x00, 0x00, 0x00, + // 0xd8: localeScriptWasComputed + 0x00, + // 0xd9: localeNumberingSystem(1) + 0x00, 0x00, 0x00, 0x00, + // 0xdd: localeNumberingSystem(2) + 0x00, 0x00, 0x00, 0x00, + + // 0xe1: padding + 0x00, 0x00, 0x00, + + // OVERLAY ENTRIES - // 0x88: 0x7f020000 -> 0x7f020000 + // 0xe4: 0x7f020000 -> 0x7f020000 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f, - // 0x90: 0x7f030000 -> 0x7f030000 + // 0xec: 0x7f030000 -> 0x7f030000 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f, - // 0x98: 0x7f030001 -> 0x7f030002 + // 0xf4: 0x7f030001 -> 0x7f030002 0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f, - // 0xa0: string pool + // 0xfc: string pool // string length, 0x04, 0x00, 0x00, 0x00, - // 0xa4 string contents "test" + // 0x100 string contents "test" 0x74, 0x65, 0x73, 0x74}; -const unsigned int kIdmapRawDataLen = 0xa8; +const unsigned int kIdmapRawDataLen = 0x104; const unsigned int kIdmapRawDataOffset = 0x54; const unsigned int kIdmapRawDataTargetCrc = 0x1234; const unsigned int kIdmapRawOverlayCrc = 0x5678; diff --git a/core/api/current.txt b/core/api/current.txt index 4d25ad7d8b37..47b66a4c6fe6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -18919,8 +18919,8 @@ package android.inputmethodservice { method public android.view.View onCreateCandidatesView(); method public android.view.View onCreateExtractTextView(); method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest(@NonNull android.os.Bundle); - method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); - method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); + method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); + method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public android.view.View onCreateInputView(); method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype); method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]); @@ -48771,6 +48771,10 @@ package android.view { field public static final int KEYCODE_STEM_2 = 266; // 0x10a field public static final int KEYCODE_STEM_3 = 267; // 0x10b field public static final int KEYCODE_STEM_PRIMARY = 264; // 0x108 + field public static final int KEYCODE_STYLUS_BUTTON_PRIMARY = 308; // 0x134 + field public static final int KEYCODE_STYLUS_BUTTON_SECONDARY = 309; // 0x135 + field public static final int KEYCODE_STYLUS_BUTTON_TAIL = 311; // 0x137 + field public static final int KEYCODE_STYLUS_BUTTON_TERTIARY = 310; // 0x136 field public static final int KEYCODE_SWITCH_CHARSET = 95; // 0x5f field public static final int KEYCODE_SYM = 63; // 0x3f field public static final int KEYCODE_SYSRQ = 120; // 0x78 @@ -52951,7 +52955,7 @@ package android.view.displayhash { package android.view.inputmethod { public class BaseInputConnection implements android.view.inputmethod.InputConnection { - ctor public BaseInputConnection(android.view.View, boolean); + ctor public BaseInputConnection(@NonNull android.view.View, boolean); method public boolean beginBatchEdit(); method public boolean clearMetaKeyStates(int); method @CallSuper public void closeConnection(); @@ -52963,24 +52967,24 @@ package android.view.inputmethod { method public boolean deleteSurroundingTextInCodePoints(int, int); method public boolean endBatchEdit(); method public boolean finishComposingText(); - method public static int getComposingSpanEnd(android.text.Spannable); - method public static int getComposingSpanStart(android.text.Spannable); + method public static int getComposingSpanEnd(@NonNull android.text.Spannable); + method public static int getComposingSpanStart(@NonNull android.text.Spannable); method public int getCursorCapsMode(int); - method public android.text.Editable getEditable(); - method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int); - method public android.os.Handler getHandler(); - method public CharSequence getSelectedText(int); + method @Nullable public android.text.Editable getEditable(); + method @Nullable public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int); + method @Nullable public android.os.Handler getHandler(); + method @Nullable public CharSequence getSelectedText(int); method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); - method public static final void removeComposingSpans(android.text.Spannable); + method public static final void removeComposingSpans(@NonNull android.text.Spannable); method public boolean reportFullscreenMode(boolean); method public boolean requestCursorUpdates(int); method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); - method public static void setComposingSpans(android.text.Spannable); + method public static void setComposingSpans(@NonNull android.text.Spannable); method public boolean setComposingText(CharSequence, int); method public boolean setSelection(int, int); } @@ -53061,6 +53065,24 @@ package android.view.inputmethod { method @NonNull public android.view.inputmethod.DeleteGesture.Builder setGranularity(int); } + public final class DeleteRangeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.graphics.RectF getDeletionEndArea(); + method @NonNull public android.graphics.RectF getDeletionStartArea(); + method public int getGranularity(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.DeleteRangeGesture> CREATOR; + } + + public static final class DeleteRangeGesture.Builder { + ctor public DeleteRangeGesture.Builder(); + method @NonNull public android.view.inputmethod.DeleteRangeGesture build(); + method @NonNull public android.view.inputmethod.DeleteRangeGesture.Builder setDeletionEndArea(@NonNull android.graphics.RectF); + method @NonNull public android.view.inputmethod.DeleteRangeGesture.Builder setDeletionStartArea(@NonNull android.graphics.RectF); + method @NonNull public android.view.inputmethod.DeleteRangeGesture.Builder setFallbackText(@Nullable String); + method @NonNull public android.view.inputmethod.DeleteRangeGesture.Builder setGranularity(int); + } + public final class EditorBoundsInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.graphics.RectF getEditorBounds(); @@ -53160,11 +53182,13 @@ package android.view.inputmethod { public abstract class HandwritingGesture { method @Nullable public String getFallbackText(); field public static final int GESTURE_TYPE_DELETE = 4; // 0x4 + field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40 field public static final int GESTURE_TYPE_INSERT = 2; // 0x2 field public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 16; // 0x10 field public static final int GESTURE_TYPE_NONE = 0; // 0x0 field public static final int GESTURE_TYPE_REMOVE_SPACE = 8; // 0x8 field public static final int GESTURE_TYPE_SELECT = 1; // 0x1 + field public static final int GESTURE_TYPE_SELECT_RANGE = 32; // 0x20 field public static final int GRANULARITY_CHARACTER = 2; // 0x2 field public static final int GRANULARITY_WORD = 1; // 0x1 } @@ -53549,6 +53573,24 @@ package android.view.inputmethod { method @NonNull public android.view.inputmethod.SelectGesture.Builder setSelectionArea(@NonNull android.graphics.RectF); } + public final class SelectRangeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { + method public int describeContents(); + method public int getGranularity(); + method @NonNull public android.graphics.RectF getSelectionEndArea(); + method @NonNull public android.graphics.RectF getSelectionStartArea(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SelectRangeGesture> CREATOR; + } + + public static final class SelectRangeGesture.Builder { + ctor public SelectRangeGesture.Builder(); + method @NonNull public android.view.inputmethod.SelectRangeGesture build(); + method @NonNull public android.view.inputmethod.SelectRangeGesture.Builder setFallbackText(@Nullable String); + method @NonNull public android.view.inputmethod.SelectRangeGesture.Builder setGranularity(int); + method @NonNull public android.view.inputmethod.SelectRangeGesture.Builder setSelectionEndArea(@NonNull android.graphics.RectF); + method @NonNull public android.view.inputmethod.SelectRangeGesture.Builder setSelectionStartArea(@NonNull android.graphics.RectF); + } + public final class SurroundingText implements android.os.Parcelable { ctor public SurroundingText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int); method public int describeContents(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f09244ad7859..b317a2561157 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1318,6 +1318,7 @@ package android.app.ambientcontext { method @NonNull public java.time.Instant getStartTime(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR; + field public static final int EVENT_BACK_DOUBLE_TAP = 3; // 0x3 field public static final int EVENT_COUGH = 1; // 0x1 field public static final int EVENT_SNORE = 2; // 0x2 field public static final int EVENT_UNKNOWN = 0; // 0x0 @@ -6665,6 +6666,15 @@ package android.media.musicrecognition { } +package android.media.projection { + + public class MediaProjectionGlobal { + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface); + method @NonNull public static android.media.projection.MediaProjectionGlobal getInstance(); + } + +} + package android.media.session { public final class MediaSessionManager { @@ -10347,6 +10357,7 @@ package android.provider { field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service"; field public static final String NAMESPACE_AUTOFILL = "autofill"; + field public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore"; field public static final String NAMESPACE_BATTERY_SAVER = "battery_saver"; field public static final String NAMESPACE_BIOMETRICS = "biometrics"; field public static final String NAMESPACE_BLOBSTORE = "blobstore"; @@ -10393,6 +10404,7 @@ package android.provider { field public static final String NAMESPACE_TELEPHONY = "telephony"; field public static final String NAMESPACE_TETHERING = "tethering"; field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier"; + field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata"; field public static final String NAMESPACE_UWB = "uwb"; field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index deb5aca53b78..d573f13b11e3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2209,6 +2209,7 @@ package android.provider { field public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; field public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; field public static final String AUTOFILL_SERVICE = "autofill_service"; + field public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled"; field public static final String CONTENT_CAPTURE_ENABLED = "content_capture_enabled"; field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services"; field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages"; @@ -2709,7 +2710,7 @@ package android.util { public class SparseArrayMap<K, V> { ctor public SparseArrayMap(); - method public void add(int, @NonNull K, @Nullable V); + method public V add(int, @NonNull K, @Nullable V); method public void clear(); method public boolean contains(int, @NonNull K); method public void delete(int); @@ -2854,7 +2855,7 @@ package android.view { method public static String actionToString(int); method public final void setDisplayId(int); field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 - field public static final int LAST_KEYCODE = 307; // 0x133 + field public static final int LAST_KEYCODE = 311; // 0x137 } public final class KeyboardShortcutGroup implements android.os.Parcelable { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 0a906bee6fad..f9b8a3006880 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -873,6 +873,8 @@ NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARG New setting keys are not allowed (Field: ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_SERVICE: New setting keys are not allowed (Field: AUTOFILL_SERVICE); use getters/setters in relevant manager class +NoSettingsProvider: android.provider.Settings.Secure#BIOMETRIC_VIRTUAL_ENABLED: + New setting keys are not allowed (Field: BIOMETRIC_VIRTUAL_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#CONTENT_CAPTURE_ENABLED: New setting keys are not allowed (Field: CONTENT_CAPTURE_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#DISABLED_PRINT_SERVICES: @@ -1011,7 +1013,7 @@ UserHandleName: android.content.ContentCaptureOptions: Classes holding a set of parameters should be called `FooParams`, was `ContentCaptureOptions` -VisiblySynchronized: PsiThisExpression:this: +VisiblySynchronized: PsiThisExpression: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths() VisiblySynchronized: android.content.res.AssetManager#getApkPaths(): Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths() diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d899dab8a61d..dfef279be7c5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4216,13 +4216,23 @@ public class ActivityManager { } }*/ + /** @hide + * Determines whether the given UID can access unexported components + * @param uid the calling UID + * @return true if the calling UID is ROOT or SYSTEM + */ + public static boolean canAccessUnexportedComponents(int uid) { + final int appId = UserHandle.getAppId(uid); + return (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID); + } + /** @hide */ @UnsupportedAppUsage public static int checkComponentPermission(String permission, int uid, int owningUid, boolean exported) { // Root, system server get to do everything. final int appId = UserHandle.getAppId(uid); - if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) { + if (canAccessUnexportedComponents(uid)) { return PackageManager.PERMISSION_GRANTED; } // Isolated processes don't get any permissions. @@ -4350,20 +4360,28 @@ public class ActivityManager { } /** - * Starts the given user in background and associate the user with the given display. + * Starts the given user in background and assign the user to the given display. * * <p>This method will allow the user to launch activities on that display, and it's typically * used only on automotive builds when the vehicle has multiple displays (you can verify if it's - * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}). + * supported by calling {@link UserManager#isUsersOnSecondaryDisplaysSupported()}). + * + * <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground + * user before starting a new one, this method does not stop the previous user running in + * background in the display, and it will return {@code false} in this case. It's up to the + * caller to call {@link #stopUser(int, boolean)} before starting a new user. * - * @return whether the user was started. + * @param userId user to be started in the display. It will return {@code false} if the user is + * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or + * does not exist. + * + * @param displayId id of the display, it must exist. + * + * @return whether the operation succeeded. Notice that if the user was already started in such + * display before, it will return {@code false}. * * @throws UnsupportedOperationException if the device does not support background users on * secondary displays. - * @throws IllegalArgumentException if the display does not exist. - * @throws IllegalStateException if the user cannot be started on that display (for example, if - * there's already a user using that display or if the user is already associated with other - * display). * * @hide */ @@ -4372,6 +4390,10 @@ public class ActivityManager { android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId, int displayId) { + if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) { + throw new UnsupportedOperationException( + "device does not support users on secondary displays"); + } try { return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId); } catch (RemoteException e) { @@ -4532,6 +4554,10 @@ public class ActivityManager { /** * Stops the given {@code userId}. * + * <p><b>NOTE:</b> on systems that support + * {@link UserManager#isUsersOnSecondaryDisplaysSupported() background users on secondary + * displays}, this method will also unassign the user from the display it was started on. + * * @hide */ @TestApi diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ee5d2b7ba327..cf5f10ba109e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -7838,7 +7838,7 @@ public final class ActivityThread extends ClientTransactionHandler Files.move(new File(oldPath).toPath(), new File(newPath).toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e2) { - Log.e(TAG, "Rename recovery failed ", e); + Log.e(TAG, "Rename recovery failed ", e2); throw e; } } else { diff --git a/core/java/android/app/AppOpInfo.java b/core/java/android/app/AppOpInfo.java index 979c910ff5f4..5268ec42e21c 100644 --- a/core/java/android/app/AppOpInfo.java +++ b/core/java/android/app/AppOpInfo.java @@ -26,6 +26,7 @@ import java.util.Objects; * Information about a particular app op. */ class AppOpInfo { + /** * A unique constant identifying this app op. */ @@ -91,6 +92,11 @@ class AppOpInfo { */ public final boolean restrictRead; + /** + * Whether to collect noteOp instances, and send them to callbacks. + */ + public final boolean forceCollectNotes; + AppOpInfo(int code, int switchCode, @NonNull String name, @@ -100,7 +106,8 @@ class AppOpInfo { AppOpsManager.RestrictionBypass allowSystemRestrictionBypass, int defaultMode, boolean disableReset, - boolean restrictRead) { + boolean restrictRead, + boolean forceCollectNotes) { if (code < OP_NONE) throw new IllegalArgumentException(); if (switchCode < OP_NONE) throw new IllegalArgumentException(); Objects.requireNonNull(name); @@ -115,6 +122,7 @@ class AppOpInfo { this.defaultMode = defaultMode; this.disableReset = disableReset; this.restrictRead = restrictRead; + this.forceCollectNotes = forceCollectNotes; } static class Builder { @@ -128,6 +136,7 @@ class AppOpInfo { private int mDefaultMode = AppOpsManager.MODE_DEFAULT; private boolean mDisableReset = false; private boolean mRestrictRead = false; + private boolean mForceCollectNotes = false; Builder(int code, @NonNull String name, @NonNull String simpleName) { if (code < OP_NONE) throw new IllegalArgumentException(); @@ -190,9 +199,15 @@ class AppOpInfo { return this; } + public Builder setForceCollectNotes(boolean value) { + this.mForceCollectNotes = value; + return this; + } + public AppOpInfo build() { return new AppOpInfo(mCode, mSwitchCode, mName, mSimpleName, mPermission, mRestriction, - mAllowSystemRestrictionBypass, mDefaultMode, mDisableReset, mRestrictRead); + mAllowSystemRestrictionBypass, mDefaultMode, mDisableReset, mRestrictRead, + mForceCollectNotes); } } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index bc59d60bebd1..28404d5338c6 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -554,7 +554,7 @@ public class AppOpsManager { * @hide */ public static int resolveFirstUnrestrictedUidState(int op) { - return UID_STATE_FOREGROUND; + return UID_STATE_MAX_LAST_NON_RESTRICTED; } /** @@ -1831,6 +1831,9 @@ public class AppOpsManager { }) private @interface ShouldCollectNoteOp {} + /** Whether noting for an appop should be collected */ + private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP]; + private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = { // RUNTIME PERMISSIONS // Contacts @@ -2281,10 +2284,19 @@ public class AppOpsManager { "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED) .setDisableReset(true).setRestrictRead(true).build(), new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, - "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED).build() + "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED) + .setForceCollectNotes(true).build() }; /** + * @hide + */ + public static boolean shouldForceCollectNoteForOp(int op) { + Preconditions.checkArgumentInRange(op, 0, _NUM_OP - 1, "opCode"); + return sAppOpInfos[op].forceCollectNotes; + } + + /** * Mapping from an app op name to the app op code. */ private static HashMap<String, Integer> sOpStrToOp = new HashMap<>(); @@ -2313,9 +2325,6 @@ public class AppOpsManager { private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>(); - /** Whether noting for an appop should be collected */ - private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP]; - static { if (sAppOpInfos.length != _NUM_OP) { throw new IllegalStateException("mAppOpInfos length " + sAppOpInfos.length diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 93381cf82764..81f61430560d 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -258,6 +258,11 @@ import java.lang.reflect.InvocationTargetException; * pressing back will pop it to return the user to whatever previous state * the activity UI was in. * + * <p> + * Fragments appearing or disappearing do not generate system events for accessibility, so set a + * title on your fragments with {@link View#setAccessibilityPaneTitle(CharSequence)} to notify + * accessibility users of these UI transitions. + * * @deprecated Use the <a href="{@docRoot}jetpack">Jetpack Fragment Library</a> * {@link androidx.fragment.app.Fragment} for consistent behavior across all devices * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index bd999fc04844..980b79ba01e8 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -522,9 +522,6 @@ interface IActivityManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd); - /** Enables server-side binder tracing for the calling uid. */ - void enableBinderTracing(); - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void suppressResizeConfigChanges(boolean suppress); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) @@ -769,6 +766,8 @@ interface IActivityManager { * * <p>Typically used only by automotive builds when the vehicle has multiple displays. */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)") boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId); } diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 13934e592d40..a51b9d3956df 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -391,7 +391,12 @@ public class PropertyInvalidatedCache<Query, Result> { private static final boolean DEBUG = false; private static final boolean VERIFY = false; - // Per-Cache performance counters. As some cache instances are declared static, + /** + * The object-private lock. + */ + private final Object mLock = new Object(); + + // Per-Cache performance counters. @GuardedBy("mLock") private long mHits = 0; @@ -410,25 +415,19 @@ public class PropertyInvalidatedCache<Query, Result> { @GuardedBy("mLock") private long mClears = 0; - // Most invalidation is done in a static context, so the counters need to be accessible. - @GuardedBy("sCorkLock") - private static final HashMap<String, Long> sInvalidates = new HashMap<>(); - /** - * Record the number of invalidate or cork calls that were nops because - * the cache was already corked. This is static because invalidation is - * done in a static context. + * Protect objects that support corking. mLock and sGlobalLock must never be taken while this + * is held. */ - @GuardedBy("sCorkLock") - private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); + private static final Object sCorkLock = new Object(); /** - * If sEnabled is false then all cache operations are stubbed out. Set - * it to false inside test processes. + * Record the number of invalidate or cork calls that were nops because the cache was already + * corked. This is static because invalidation is done in a static context. Entries are + * indexed by the cache property. */ - private static boolean sEnabled = true; - - private static final Object sCorkLock = new Object(); + @GuardedBy("sCorkLock") + private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); /** * A map of cache keys that we've "corked". (The values are counts.) When a cache key is @@ -440,21 +439,39 @@ public class PropertyInvalidatedCache<Query, Result> { private static final HashMap<String, Integer> sCorks = new HashMap<>(); /** + * A lock for the global list of caches and cache keys. This must never be taken inside mLock + * or sCorkLock. + */ + private static final Object sGlobalLock = new Object(); + + /** * A map of cache keys that have been disabled in the local process. When a key is * disabled locally, existing caches are disabled and the key is saved in this map. * Future cache instances that use the same key will be disabled in their constructor. */ - @GuardedBy("sCorkLock") + @GuardedBy("sGlobalLock") private static final HashSet<String> sDisabledKeys = new HashSet<>(); /** * Weakly references all cache objects in the current process, allowing us to iterate over * them all for purposes like issuing debug dumps and reacting to memory pressure. */ - @GuardedBy("sCorkLock") + @GuardedBy("sGlobalLock") private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); - private final Object mLock = new Object(); + /** + * Counts of the number of times a cache key was invalidated. Invalidation occurs in a static + * context with no cache object available, so this is a static map. Entries are indexed by + * the cache property. + */ + @GuardedBy("sGlobalLock") + private static final HashMap<String, Long> sInvalidates = new HashMap<>(); + + /** + * If sEnabled is false then all cache operations are stubbed out. Set + * it to false inside test processes. + */ + private static boolean sEnabled = true; /** * Name of the property that holds the unique value that we use to invalidate the cache. @@ -595,14 +612,17 @@ public class PropertyInvalidatedCache<Query, Result> { }; } - // Register the map in the global list. If the cache is disabled globally, disable it - // now. + /** + * Register the map in the global list. If the cache is disabled globally, disable it + * now. This method is only ever called from the constructor, which means no other thread has + * access to the object yet. It can safely be modified outside any lock. + */ private void registerCache() { - synchronized (sCorkLock) { - sCaches.put(this, null); + synchronized (sGlobalLock) { if (sDisabledKeys.contains(mCacheName)) { disableInstance(); } + sCaches.put(this, null); } } @@ -797,8 +817,9 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Disable the use of this cache in this process. This method is using during - * testing. To disable a cache in normal code, use disableLocal(). + * Disable the use of this cache in this process. This method is using internally and during + * testing. To disable a cache in normal code, use disableLocal(). A disabled cache cannot + * be re-enabled. * @hide */ @TestApi @@ -811,17 +832,23 @@ public class PropertyInvalidatedCache<Query, Result> { /** * Disable the local use of all caches with the same name. All currently registered caches - * using the key will be disabled now, and all future cache instances that use the key will be - * disabled in their constructor. + * with the name will be disabled now, and all future cache instances that use the name will + * be disabled in their constructor. */ private static final void disableLocal(@NonNull String name) { - synchronized (sCorkLock) { - sDisabledKeys.add(name); + synchronized (sGlobalLock) { + if (sDisabledKeys.contains(name)) { + // The key is already in recorded so there is no further work to be done. + return; + } for (PropertyInvalidatedCache cache : sCaches.keySet()) { if (name.equals(cache.mCacheName)) { cache.disableInstance(); } } + // Record the disabled key after the iteration. If an exception occurs during the + // iteration above, and the code is retried, the function should not exit early. + sDisabledKeys.add(name); } } @@ -834,7 +861,7 @@ public class PropertyInvalidatedCache<Query, Result> { */ @TestApi public final void forgetDisableLocal() { - synchronized (sCorkLock) { + synchronized (sGlobalLock) { sDisabledKeys.remove(mCacheName); } } @@ -851,9 +878,9 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Disable this cache in the current process, and all other caches that use the same - * name. This does not affect caches that have a different name but use the same - * property. + * Disable this cache in the current process, and all other present and future caches that use + * the same name. This does not affect caches that have a different name but use the same + * property. Once disabled, a cache cannot be reenabled. * @hide */ @TestApi @@ -1381,10 +1408,9 @@ public class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ + @GuardedBy("sGlobalLock") private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { - synchronized (sCorkLock) { - return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); - } + return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } /** @@ -1540,7 +1566,7 @@ public class PropertyInvalidatedCache<Query, Result> { boolean detail = anyDetailed(args); ArrayList<PropertyInvalidatedCache> activeCaches; - synchronized (sCorkLock) { + synchronized (sGlobalLock) { activeCaches = getActiveCaches(); if (!detail) { dumpCorkInfo(pw); diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 8d57e32a763c..d17f2ba1b401 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -45,6 +45,7 @@ import android.view.WindowAnimationFrameStats; import android.view.WindowContentFrameStats; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.IAccessibilityManager; +import android.window.ScreenCapture; import libcore.io.IoUtils; @@ -220,13 +221,13 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { int width = crop.width(); int height = crop.height(); final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); - final SurfaceControl.DisplayCaptureArgs captureArgs = - new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) + final ScreenCapture.DisplayCaptureArgs captureArgs = + new ScreenCapture.DisplayCaptureArgs.Builder(displayToken) .setSourceCrop(crop) .setSize(width, height) .build(); - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureDisplay(captureArgs); + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureDisplay(captureArgs); return screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); } finally { Binder.restoreCallingIdentity(identity); @@ -242,11 +243,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { throwIfNotConnectedLocked(); } - SurfaceControl.ScreenshotHardwareBuffer captureBuffer; + ScreenCapture.ScreenshotHardwareBuffer captureBuffer; final long identity = Binder.clearCallingIdentity(); try { - captureBuffer = SurfaceControl.captureLayers( - new SurfaceControl.LayerCaptureArgs.Builder(surfaceControl) + captureBuffer = ScreenCapture.captureLayers( + new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl) .setChildrenOnly(false) .build()); } finally { diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index af48fde6423a..865e1fbaf74e 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -63,8 +63,6 @@ public final class AmbientContextEvent implements Parcelable { * The integer indicating a double-tap event was detected. * For detecting this event type, there's no specific consent activity to request access, but * the consent is implied through the double tap toggle in the Settings app. - * - * @hide */ public static final int EVENT_BACK_DOUBLE_TAP = 3; diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java index 6b0f78f2c1c3..0f66fcbdbec9 100644 --- a/core/java/android/ddm/DdmHandleViewDebug.java +++ b/core/java/android/ddm/DdmHandleViewDebug.java @@ -16,12 +16,16 @@ package android.ddm; +import static com.android.internal.util.Preconditions.checkArgument; + import android.util.Log; import android.view.View; import android.view.ViewDebug; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; +import com.android.internal.annotations.VisibleForTesting; + import org.apache.harmony.dalvik.ddmc.Chunk; import org.apache.harmony.dalvik.ddmc.ChunkHandler; import org.apache.harmony.dalvik.ddmc.DdmServer; @@ -34,6 +38,7 @@ import java.io.OutputStreamWriter; import java.lang.reflect.Method; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; /** * Handle various requests related to profiling / debugging of the view system. @@ -123,14 +128,15 @@ public class DdmHandleViewDebug extends DdmHandle { } if (type == CHUNK_VURT) { - if (op == VURT_DUMP_HIERARCHY) + if (op == VURT_DUMP_HIERARCHY) { return dumpHierarchy(rootView, in); - else if (op == VURT_CAPTURE_LAYERS) + } else if (op == VURT_CAPTURE_LAYERS) { return captureLayers(rootView); - else if (op == VURT_DUMP_THEME) + } else if (op == VURT_DUMP_THEME) { return dumpTheme(rootView); - else + } else { return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op); + } } final View targetView = getTargetView(rootView, in); @@ -207,9 +213,9 @@ public class DdmHandleViewDebug extends DdmHandle { /** * Returns the view hierarchy and/or view properties starting at the provided view. * Based on the input options, the return data may include: - * - just the view hierarchy - * - view hierarchy & the properties for each of the views - * - just the view properties for a specific view. + * - just the view hierarchy + * - view hierarchy & the properties for each of the views + * - just the view properties for a specific view. * TODO: Currently this only returns views starting at the root, need to fix so that * it can return properties of any view. */ @@ -220,7 +226,7 @@ public class DdmHandleViewDebug extends DdmHandle { long start = System.currentTimeMillis(); - ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024); + ByteArrayOutputStream b = new ByteArrayOutputStream(2 * 1024 * 1024); try { if (v2) { ViewDebug.dumpv2(rootView, b); @@ -304,17 +310,47 @@ public class DdmHandleViewDebug extends DdmHandle { * Invokes provided method on the view. * The method name and its arguments are passed in as inputs via the byte buffer. * The buffer contains:<ol> - * <li> len(method name) </li> - * <li> method name </li> - * <li> # of args </li> - * <li> arguments: Each argument comprises of a type specifier followed by the actual argument. - * The type specifier is a single character as used in JNI: - * (Z - boolean, B - byte, C - char, S - short, I - int, J - long, - * F - float, D - double). <p> - * The type specifier is followed by the actual value of argument. - * Booleans are encoded via bytes with 0 indicating false.</li> + * <li> len(method name) </li> + * <li> method name (encoded as UTF-16 2-byte characters) </li> + * <li> # of args </li> + * <li> arguments: Each argument comprises of a type specifier followed by the actual argument. + * The type specifier is one character modelled after JNI signatures: + * <ul> + * <li>[ - array<br> + * This is followed by a second character according to this spec, indicating the + * array type, then the array length as an Int, followed by a repeated encoding + * of the actual data. + * WARNING: Only <b>byte[]</b> is supported currently. + * </li> + * <li>Z - boolean<br> + * Booleans are encoded via bytes with 0 indicating false</li> + * <li>B - byte</li> + * <li>C - char</li> + * <li>S - short</li> + * <li>I - int</li> + * <li>J - long</li> + * <li>F - float</li> + * <li>D - double</li> + * <li>V - void<br> + * NOT followed by a value. Only used for return types</li> + * <li>R - String (not a real JNI type, but added for convenience)<br> + * Strings are encoded as an unsigned short of the number of <b>bytes</b>, + * followed by the actual UTF-8 encoded bytes. + * WARNING: This is the same encoding as produced by + * ViewHierarchyEncoder#writeString. However, note that this encoding is + * different to what DdmHandle#getString() expects, which is used in other places + * in this class. + * WARNING: Since the length is the number of UTF-8 encoded bytes, Strings can + * contain up to 64k ASCII characters, yet depending on the actual data, the true + * maximum might be as little as 21844 unicode characters. + * <b>null</b> String objects are encoded as an empty string + * </li> + * </ul> + * </li> * </ol> * Methods that take no arguments need only specify the method name. + * + * The return value is encoded the same way as a single parameter (type + value) */ private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) { int l = in.getInt(); @@ -327,54 +363,17 @@ public class DdmHandleViewDebug extends DdmHandle { args = new Object[0]; } else { int nArgs = in.getInt(); - argTypes = new Class<?>[nArgs]; args = new Object[nArgs]; - for (int i = 0; i < nArgs; i++) { - char c = in.getChar(); - switch (c) { - case 'Z': - argTypes[i] = boolean.class; - args[i] = in.get() == 0 ? false : true; - break; - case 'B': - argTypes[i] = byte.class; - args[i] = in.get(); - break; - case 'C': - argTypes[i] = char.class; - args[i] = in.getChar(); - break; - case 'S': - argTypes[i] = short.class; - args[i] = in.getShort(); - break; - case 'I': - argTypes[i] = int.class; - args[i] = in.getInt(); - break; - case 'J': - argTypes[i] = long.class; - args[i] = in.getLong(); - break; - case 'F': - argTypes[i] = float.class; - args[i] = in.getFloat(); - break; - case 'D': - argTypes[i] = double.class; - args[i] = in.getDouble(); - break; - default: - Log.e(TAG, "arg " + i + ", unrecognized type: " + c); - return createFailChunk(ERR_INVALID_PARAM, - "Unsupported parameter type (" + c + ") to invoke view method."); - } + try { + deserializeMethodParameters(args, argTypes, in); + } catch (ViewMethodInvocationSerializationException e) { + return createFailChunk(ERR_INVALID_PARAM, e.getMessage()); } } - Method method = null; + Method method; try { method = targetView.getClass().getMethod(methodName, argTypes); } catch (NoSuchMethodException e) { @@ -384,7 +383,10 @@ public class DdmHandleViewDebug extends DdmHandle { } try { - ViewDebug.invokeViewMethod(targetView, method, args); + Object result = ViewDebug.invokeViewMethod(targetView, method, args); + Class<?> returnType = method.getReturnType(); + byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result)); + return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length); } catch (Exception e) { Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage()); String msg = e.getCause().getMessage(); @@ -393,8 +395,6 @@ public class DdmHandleViewDebug extends DdmHandle { } return createFailChunk(ERR_EXCEPTION, msg); } - - return null; } private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) { @@ -406,7 +406,7 @@ public class DdmHandleViewDebug extends DdmHandle { } catch (Exception e) { Log.e(TAG, "Exception setting layout parameter: " + e); return createFailChunk(ERR_EXCEPTION, "Error accessing field " - + param + ":" + e.getMessage()); + + param + ":" + e.getMessage()); } return null; @@ -431,4 +431,175 @@ public class DdmHandleViewDebug extends DdmHandle { byte[] data = b.toByteArray(); return new Chunk(CHUNK_VUOP, data, 0, data.length); } + + /** + * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in} + * buffer. + * + * The length of {@code args} determines how many arguments are read. The {@code argTypes} must + * be the same length, and will be set to the argument types of the data read. + * + * @hide + */ + @VisibleForTesting + public static void deserializeMethodParameters( + Object[] args, Class<?>[] argTypes, ByteBuffer in) throws + ViewMethodInvocationSerializationException { + checkArgument(args.length == argTypes.length); + + for (int i = 0; i < args.length; i++) { + char typeSignature = in.getChar(); + boolean isArray = typeSignature == SIG_ARRAY; + if (isArray) { + char arrayType = in.getChar(); + if (arrayType != SIG_BYTE) { + // This implementation only supports byte-arrays for now. + throw new ViewMethodInvocationSerializationException( + "Unsupported array parameter type (" + typeSignature + + ") to invoke view method @argument " + i); + } + + int arrayLength = in.getInt(); + if (arrayLength > in.remaining()) { + // The sender did not actually sent the specified amount of bytes. This + // avoids a malformed packet to trigger an out-of-memory error. + throw new BufferUnderflowException(); + } + + byte[] byteArray = new byte[arrayLength]; + in.get(byteArray); + + argTypes[i] = byte[].class; + args[i] = byteArray; + } else { + switch (typeSignature) { + case SIG_BOOLEAN: + argTypes[i] = boolean.class; + args[i] = in.get() != 0; + break; + case SIG_BYTE: + argTypes[i] = byte.class; + args[i] = in.get(); + break; + case SIG_CHAR: + argTypes[i] = char.class; + args[i] = in.getChar(); + break; + case SIG_SHORT: + argTypes[i] = short.class; + args[i] = in.getShort(); + break; + case SIG_INT: + argTypes[i] = int.class; + args[i] = in.getInt(); + break; + case SIG_LONG: + argTypes[i] = long.class; + args[i] = in.getLong(); + break; + case SIG_FLOAT: + argTypes[i] = float.class; + args[i] = in.getFloat(); + break; + case SIG_DOUBLE: + argTypes[i] = double.class; + args[i] = in.getDouble(); + break; + case SIG_STRING: { + argTypes[i] = String.class; + int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort()); + byte[] rawStringBuffer = new byte[stringUtf8ByteCount]; + in.get(rawStringBuffer); + args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8); + break; + } + default: + Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature); + throw new ViewMethodInvocationSerializationException( + "Unsupported parameter type (" + typeSignature + + ") to invoke view method."); + } + } + + } + } + + /** + * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD. + * @hide + */ + @VisibleForTesting + public static byte[] serializeReturnValue(Class<?> type, Object value) + throws ViewMethodInvocationSerializationException, IOException { + ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024); + DataOutputStream dos = new DataOutputStream(byteOutStream); + + if (type.isArray()) { + if (!type.equals(byte[].class)) { + // Only byte arrays are supported currently. + throw new ViewMethodInvocationSerializationException( + "Unsupported array return type (" + type + ")"); + } + byte[] byteArray = (byte[]) value; + dos.writeChar(SIG_ARRAY); + dos.writeChar(SIG_BYTE); + dos.writeInt(byteArray.length); + dos.write(byteArray); + } else if (boolean.class.equals(type)) { + dos.writeChar(SIG_BOOLEAN); + dos.write((boolean) value ? 1 : 0); + } else if (byte.class.equals(type)) { + dos.writeChar(SIG_BYTE); + dos.writeByte((byte) value); + } else if (char.class.equals(type)) { + dos.writeChar(SIG_CHAR); + dos.writeChar((char) value); + } else if (short.class.equals(type)) { + dos.writeChar(SIG_SHORT); + dos.writeShort((short) value); + } else if (int.class.equals(type)) { + dos.writeChar(SIG_INT); + dos.writeInt((int) value); + } else if (long.class.equals(type)) { + dos.writeChar(SIG_LONG); + dos.writeLong((long) value); + } else if (double.class.equals(type)) { + dos.writeChar(SIG_DOUBLE); + dos.writeDouble((double) value); + } else if (float.class.equals(type)) { + dos.writeChar(SIG_FLOAT); + dos.writeFloat((float) value); + } else if (String.class.equals(type)) { + dos.writeChar(SIG_STRING); + dos.writeUTF(value != null ? (String) value : ""); + } else { + dos.writeChar(SIG_VOID); + } + + return byteOutStream.toByteArray(); + } + + // Prefixes for simple primitives. These match the JNI definitions. + private static final char SIG_ARRAY = '['; + private static final char SIG_BOOLEAN = 'Z'; + private static final char SIG_BYTE = 'B'; + private static final char SIG_SHORT = 'S'; + private static final char SIG_CHAR = 'C'; + private static final char SIG_INT = 'I'; + private static final char SIG_LONG = 'J'; + private static final char SIG_FLOAT = 'F'; + private static final char SIG_DOUBLE = 'D'; + private static final char SIG_VOID = 'V'; + // Prefixes for some commonly used objects + private static final char SIG_STRING = 'R'; + + /** + * @hide + */ + @VisibleForTesting + public static class ViewMethodInvocationSerializationException extends Exception { + ViewMethodInvocationSerializationException(String message) { + super(message); + } + } } diff --git a/core/java/android/ddm/OWNERS b/core/java/android/ddm/OWNERS new file mode 100644 index 000000000000..369025bd21f4 --- /dev/null +++ b/core/java/android/ddm/OWNERS @@ -0,0 +1 @@ +per-file DdmHandleViewDebug.java = michschn@google.com diff --git a/core/java/android/hardware/BatteryState.java b/core/java/android/hardware/BatteryState.java index aa7535982452..956fefca1f41 100644 --- a/core/java/android/hardware/BatteryState.java +++ b/core/java/android/hardware/BatteryState.java @@ -62,7 +62,8 @@ public abstract class BatteryState { * * @return the battery status. */ - public abstract @BatteryStatus int getStatus(); + @BatteryStatus + public abstract int getStatus(); /** * Get remaining battery capacity as float percentage [0.0f, 1.0f] of total capacity @@ -70,5 +71,6 @@ public abstract class BatteryState { * * @return the battery capacity. */ - public abstract @FloatRange(from = -1.0f, to = 1.0f) float getCapacity(); + @FloatRange(from = -1.0f, to = 1.0f) + public abstract float getCapacity(); } diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index 98f571b80949..c59d7571ee6d 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -217,7 +217,8 @@ public interface BiometricFingerprintConstants { FINGERPRINT_ACQUIRED_START, FINGERPRINT_ACQUIRED_UNKNOWN, FINGERPRINT_ACQUIRED_IMMOBILE, - FINGERPRINT_ACQUIRED_TOO_BRIGHT}) + FINGERPRINT_ACQUIRED_TOO_BRIGHT, + FINGERPRINT_ACQUIRED_POWER_PRESSED}) @Retention(RetentionPolicy.SOURCE) @interface FingerprintAcquired {} @@ -302,6 +303,13 @@ public interface BiometricFingerprintConstants { int FINGERPRINT_ACQUIRED_TOO_BRIGHT = 10; /** + * For sensors that have the power button co-located with their sensor, this event will + * be sent during enrollment. + * @hide + */ + int FINGERPRINT_ACQUIRED_POWER_PRESSED = 11; + + /** * @hide */ int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 52cef0f1efd0..d57a272217e2 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -28,9 +28,9 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; -import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.window.DisplayWindowPolicyController; +import android.window.ScreenCapture; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -118,7 +118,7 @@ public abstract class DisplayManagerInternal { * @param displayId The display id to take the screenshot of. * @return The buffer or null if we have failed. */ - public abstract SurfaceControl.ScreenshotHardwareBuffer systemScreenshot(int displayId); + public abstract ScreenCapture.ScreenshotHardwareBuffer systemScreenshot(int displayId); /** * General screenshot functionality that excludes secure layers and applies appropriate @@ -127,7 +127,7 @@ public abstract class DisplayManagerInternal { * @param displayId The display id to take the screenshot of. * @return The buffer or null if we have failed. */ - public abstract SurfaceControl.ScreenshotHardwareBuffer userScreenshot(int displayId); + public abstract ScreenCapture.ScreenshotHardwareBuffer userScreenshot(int displayId); /** * Returns information about the specified logical display. diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index ca3e58094400..fa3c4506d6e9 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -184,4 +184,9 @@ interface IDisplayManager { // Query for DISPLAY_DECORATION support. DisplayDecorationSupport getDisplayDecorationSupport(int displayId); + + // This method is to support behavior that was calling hidden APIs. The caller was requesting + // to set the layerStack after the display was created, which is not something we support in + // DMS. This should be deleted in V release. + void setDisplayIdToMirror(in IBinder token, int displayId); } diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java index 71cbd20e8005..02ab8be5fd1a 100644 --- a/core/java/android/hardware/display/VirtualDisplay.java +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -68,6 +68,13 @@ public final class VirtualDisplay { } /** + * @hide + */ + public IVirtualDisplayCallback getToken() { + return mToken; + } + + /** * Sets the surface that backs the virtual display. * <p> * Detaching the surface that backs a virtual display has a similar effect to diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 72d812269374..0fd164de8ffb 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -1538,6 +1538,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing case FINGERPRINT_ACQUIRED_TOO_BRIGHT: return context.getString( com.android.internal.R.string.fingerprint_acquired_too_bright); + case FINGERPRINT_ACQUIRED_POWER_PRESSED: + return context.getString( + com.android.internal.R.string.fingerprint_acquired_power_press); case FINGERPRINT_ACQUIRED_VENDOR: { String[] msgArray = context.getResources().getStringArray( com.android.internal.R.array.fingerprint_acquired_vendor); diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java index 5ce38e9a279a..6f758de63c95 100644 --- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java @@ -27,6 +27,7 @@ import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; @@ -36,6 +37,7 @@ import android.view.inputmethod.InsertGesture; import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; @@ -636,7 +638,9 @@ final class IRemoteInputConnectionInvoker { /** * Invokes one of {@link IRemoteInputConnection#performHandwritingSelectGesture}, + * {@link IRemoteInputConnection#performHandwritingSelectRangeGesture}, * {@link IRemoteInputConnection#performHandwritingDeleteGesture}, + * {@link IRemoteInputConnection#performHandwritingDeleteRangeGesture}, * {@link IRemoteInputConnection#performHandwritingInsertGesture}, * {@link IRemoteInputConnection#performHandwritingRemoveSpaceGesture}, * {@link IRemoteInputConnection#performHandwritingJoinOrSplitGesture}. @@ -655,12 +659,18 @@ final class IRemoteInputConnectionInvoker { if (gesture instanceof SelectGesture) { mConnection.performHandwritingSelectGesture( createHeader(), (SelectGesture) gesture, resultReceiver); + } else if (gesture instanceof SelectRangeGesture) { + mConnection.performHandwritingSelectRangeGesture( + createHeader(), (SelectRangeGesture) gesture, resultReceiver); } else if (gesture instanceof InsertGesture) { mConnection.performHandwritingInsertGesture( createHeader(), (InsertGesture) gesture, resultReceiver); } else if (gesture instanceof DeleteGesture) { mConnection.performHandwritingDeleteGesture( createHeader(), (DeleteGesture) gesture, resultReceiver); + } else if (gesture instanceof DeleteRangeGesture) { + mConnection.performHandwritingDeleteRangeGesture( + createHeader(), (DeleteRangeGesture) gesture, resultReceiver); } else if (gesture instanceof RemoveSpaceGesture) { mConnection.performHandwritingRemoveSpaceGesture( createHeader(), (RemoveSpaceGesture) gesture, resultReceiver); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 3a157d30d4c4..7436601f5352 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1744,7 +1744,16 @@ public class InputMethodService extends AbstractInputMethodService { /** * Implement to return our standard {@link InputMethodImpl}. Subclasses * can override to provide their own customized version. + * + * @deprecated IME developers don't need to override this method to get callbacks information. + * Most methods in {@link InputMethodImpl} have corresponding callbacks. + * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()}, + * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc. + * + * <p>Starting from Android U and later, override this method won't guarantee that IME works + * as previous platform behavior.</p> */ + @Deprecated @Override public AbstractInputMethodImpl onCreateInputMethodInterface() { return new InputMethodImpl(); @@ -1753,7 +1762,15 @@ public class InputMethodService extends AbstractInputMethodService { /** * Implement to return our standard {@link InputMethodSessionImpl}. Subclasses * can override to provide their own customized version. + * + * @deprecated IME developers don't need to override this method to get callbacks information. + * Most methods in {@link InputMethodSessionImpl} have corresponding callbacks. + * Use {@link InputMethodService#onFinishInput()}, + * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])}, + * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)}, + * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead. */ + @Deprecated @Override public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() { return new InputMethodSessionImpl(); diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 4f26ad2b4f52..d2e5f9e4ea40 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -22,7 +22,6 @@ import android.annotation.SystemApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; import android.util.ExceptionUtils; -import android.util.IntArray; import android.util.Log; import android.util.Slog; @@ -144,9 +143,6 @@ public class Binder implements IBinder { */ private static volatile boolean sStackTrackingEnabled = false; - private static final Object sTracingUidsWriteLock = new Object(); - private static volatile IntArray sTracingUidsImmutable = new IntArray(); - /** * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to * {@link TransactionTracker}. @@ -167,17 +163,6 @@ public class Binder implements IBinder { } /** - * @hide - */ - public static void enableTracingForUid(int uid) { - synchronized (sTracingUidsWriteLock) { - final IntArray copy = sTracingUidsImmutable.clone(); - copy.add(uid); - sTracingUidsImmutable = copy; - } - } - - /** * Check if binder transaction stack tracking is enabled. * * @hide @@ -187,13 +172,6 @@ public class Binder implements IBinder { } /** - * @hide - */ - public static boolean isTracingEnabled(int callingUid) { - return sTracingUidsImmutable.indexOf(callingUid) != -1; - } - - /** * Get the binder transaction tracker for this process. * * @hide diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index ecea054db216..2b75a23505d6 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -377,7 +377,7 @@ public final class SystemClock { } long currentNanos = elapsedRealtimeNanos(); long deltaMs = (currentNanos - time.getElapsedRealtimeNanos()) / 1000000L; - return time.getTime() + deltaMs; + return time.getUnixEpochTimeMillis() + deltaMs; } }; } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 0a6a405fbce6..2c0be870836a 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -541,7 +541,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) { - obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget(); + obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, Boolean.valueOf(mute)) + .sendToTarget(); } } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index d125cbb80a27..760ed26be293 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -776,6 +776,14 @@ public final class DeviceConfig { public static final String NAMESPACE_WEAR = "wear"; /** + * Namespace for features relating to MBA transparency metadata. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata"; + + /** * Namespace for the input method manager platform features. * * @hide @@ -783,6 +791,14 @@ public final class DeviceConfig { @TestApi public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager"; + /** + * Namespace for backup and restore service related features. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a99d0f0a5656..3a9af7ef4ed2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9825,6 +9825,7 @@ public final class Settings { * Whether or not virtual sensors are enabled. * @hide */ + @TestApi @Readable public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled"; diff --git a/core/java/android/security/keymaster/OWNERS b/core/java/android/security/keymaster/OWNERS index 65129a46d113..c4d605c952ad 100644 --- a/core/java/android/security/keymaster/OWNERS +++ b/core/java/android/security/keymaster/OWNERS @@ -1,5 +1,5 @@ # Bug component: 189335 swillden@google.com -jdanis@google.com +eranm@google.com jbires@google.com diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 267b2ff818a6..4d33bfd1e94a 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -91,6 +91,12 @@ public final class Condition implements Parcelable { public final int icon; /** + * The maximum string length for any string contained in this condition. + * @hide + */ + public static final int MAX_STRING_LENGTH = 1000; + + /** * An object representing the current state of a {@link android.app.AutomaticZenRule}. * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule * @param summary a user visible description of the rule state. @@ -104,16 +110,19 @@ public final class Condition implements Parcelable { if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); - this.id = id; - this.summary = summary; - this.line1 = line1; - this.line2 = line2; + this.id = getTrimmedUri(id); + this.summary = getTrimmedString(summary); + this.line1 = getTrimmedString(line1); + this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; this.flags = flags; } public Condition(Parcel source) { + // This constructor passes all fields directly into the constructor that takes all the + // fields as arguments; that constructor will trim each of the input strings to + // max length if necessary. this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class), source.readString(), source.readString(), @@ -240,4 +249,25 @@ public final class Condition implements Parcelable { return new Condition[size]; } }; + + /** + * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. + */ + private static String getTrimmedString(String input) { + if (input != null && input.length() > MAX_STRING_LENGTH) { + return input.substring(0, MAX_STRING_LENGTH); + } + return input; + } + + /** + * Returns a truncated copy of the Uri by trimming the string representation to the maximum + * string length. + */ + private static Uri getTrimmedUri(Uri input) { + if (input != null && input.toString().length() > MAX_STRING_LENGTH) { + return Uri.parse(getTrimmedString(input.toString())); + } + return input; + } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index be8c140e8770..e4c26e031738 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -93,6 +93,7 @@ import android.view.WindowLayout; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.ClientWindowFrames; +import android.window.ScreenCapture; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.HandlerCaller; @@ -1989,9 +1990,9 @@ public abstract class WallpaperService extends Service { cleanUpScreenshotSurfaceControl(); } - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureLayers( - new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl) + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureLayers( + new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl) // Needed because SurfaceFlinger#validateScreenshotPermissions // uses this parameter to check whether a caller only attempts // to screenshot itself when call doesn't come from the system. diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index 537dffc4abf1..d48d566fd860 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -170,7 +170,7 @@ public class DateFormat { * mean using 12-hour in some locales and, in this case, is duplicated as the 'a' field. */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT) + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long DISALLOW_DUPLICATE_FIELD_IN_SKELETON = 170233598L; /** diff --git a/core/java/android/util/SparseArrayMap.java b/core/java/android/util/SparseArrayMap.java index e5bb9f453a88..1a2c4df96b36 100644 --- a/core/java/android/util/SparseArrayMap.java +++ b/core/java/android/util/SparseArrayMap.java @@ -34,14 +34,20 @@ import java.util.function.Consumer; public class SparseArrayMap<K, V> { private final SparseArray<ArrayMap<K, V>> mData = new SparseArray<>(); - /** Add an entry associating obj with the int-K pair. */ - public void add(int key, @NonNull K mapKey, @Nullable V obj) { + /** + * Add an entry associating obj with the int-K pair. + * + * @return the previous value associated with key, or null if there was no mapping for key. + * (A null return can also indicate that the map previously associated null with key, if the + * implementation supports null values.) + */ + public V add(int key, @NonNull K mapKey, @Nullable V obj) { ArrayMap<K, V> data = mData.get(key); if (data == null) { data = new ArrayMap<>(); mData.put(key, data); } - data.put(mapKey, obj); + return data.put(mapKey, obj); } /** Remove all entries from the map. */ diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index f9bb880b1881..57ba7e9e816f 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -653,7 +653,7 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if ((mCurrentDownEvent == null) || mInLongPress || mInContextClick) { + if (mInLongPress || mInContextClick) { break; } @@ -736,9 +736,6 @@ public class GestureDetector { break; case MotionEvent.ACTION_UP: - if (mCurrentDownEvent == null) { - break; - } mStillDown = false; MotionEvent currentUpEvent = MotionEvent.obtain(ev); if (mIsDoubleTapping) { diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 229de31ddcba..b2a26fa665f8 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -114,6 +114,7 @@ interface IWindowManager @UnsupportedAppUsage int getInitialDisplayDensity(int displayId); int getBaseDisplayDensity(int displayId); + int getDisplayIdByUniqueId(String uniqueId); void setForcedDisplayDensityForUser(int displayId, int density, int userId); void clearForcedDisplayDensityForUser(int displayId, int userId); void setForcedDisplayScalingMode(int displayId, int mode); // 0 = auto, 1 = disable diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 38911e07eb64..42c37fd97213 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -1072,7 +1072,8 @@ public final class InputDevice implements Parcelable { * * @return The lights manager associated with the device, never null. */ - public @NonNull LightsManager getLightsManager() { + @NonNull + public LightsManager getLightsManager() { if (mLightsManager == null) { mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId); } @@ -1090,7 +1091,8 @@ public final class InputDevice implements Parcelable { * * @return The sensor manager service associated with the device, never null. */ - public @NonNull SensorManager getSensorManager() { + @NonNull + public SensorManager getSensorManager() { synchronized (mMotionRanges) { if (mSensorManager == null) { mSensorManager = InputManager.getInstance().getInputDeviceSensorManager(mId); diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 6c238e5698c5..9789b5670fbb 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -872,13 +872,33 @@ public class KeyEvent extends InputEvent implements Parcelable { public static final int KEYCODE_KEYBOARD_BACKLIGHT_UP = 306; /** Key code constant: Keyboard backlight toggle */ public static final int KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE = 307; + /** + * Key code constant: The primary button on the barrel of a stylus. + * This is usually the button closest to the tip of the stylus. + */ + public static final int KEYCODE_STYLUS_BUTTON_PRIMARY = 308; + /** + * Key code constant: The secondary button on the barrel of a stylus. + * This is usually the second button from the tip of the stylus. + */ + public static final int KEYCODE_STYLUS_BUTTON_SECONDARY = 309; + /** + * Key code constant: The tertiary button on the barrel of a stylus. + * This is usually the third button from the tip of the stylus. + */ + public static final int KEYCODE_STYLUS_BUTTON_TERTIARY = 310; + /** + * Key code constant: A button on the tail end of a stylus. + * The use of this button does not usually correspond to the function of an eraser. + */ + public static final int KEYCODE_STYLUS_BUTTON_TAIL = 311; /** * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent. * @hide */ @TestApi - public static final int LAST_KEYCODE = KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE; + public static final int LAST_KEYCODE = KEYCODE_STYLUS_BUTTON_TAIL; // NOTE: If you add a new keycode here you must also add it to: // isSystem() diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index aef38eb2e2d2..fb0cac3ba3f4 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -22,6 +22,7 @@ import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; +import static android.view.Display.INVALID_DISPLAY; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; @@ -35,7 +36,7 @@ import android.annotation.Nullable; import android.annotation.Size; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; -import android.graphics.Bitmap; +import android.content.Context; import android.graphics.ColorSpace; import android.graphics.GraphicBuffer; import android.graphics.Matrix; @@ -48,15 +49,23 @@ import android.hardware.DataSpace; import android.hardware.HardwareBuffer; import android.hardware.SyncFence; import android.hardware.display.DeviceProductInfo; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; +import android.hardware.display.IDisplayManager; +import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplay; import android.hardware.graphics.common.DisplayDecorationSupport; +import android.media.projection.MediaProjectionGlobal; import android.opengl.EGLDisplay; import android.opengl.EGLSync; import android.os.Build; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.util.SparseIntArray; @@ -80,9 +89,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** @@ -107,10 +114,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeRelease(long nativeObject); private static native void nativeDisconnect(long nativeObject); private static native void nativeUpdateDefaultBufferSize(long nativeObject, int width, int height); - private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, - ScreenCaptureListener captureListener); - private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, - ScreenCaptureListener captureListener); + private static native long nativeMirrorSurface(long mirrorOfObject); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); @@ -172,8 +176,6 @@ public final class SurfaceControl implements Parcelable { private static native long[] nativeGetPhysicalDisplayIds(); private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId); - private static native IBinder nativeCreateDisplay(String name, boolean secure); - private static native void nativeDestroyDisplay(IBinder displayToken); private static native void nativeSetDisplaySurface(long transactionObj, IBinder displayToken, long nativeSurfaceObject); private static native void nativeSetDisplayLayerStack(long transactionObj, @@ -521,7 +523,7 @@ public final class SurfaceControl implements Parcelable { * be copied. In particular, screenshots and secondary, non-secure displays will render black * content instead of the surface content. * - * @see #createDisplay(String, boolean) + * @see com.android.server.display.DisplayControl#createDisplay(String, boolean) * @hide */ public static final int SECURE = 0x00000080; @@ -781,412 +783,6 @@ public final class SurfaceControl implements Parcelable { public static final int METADATA_GAME_MODE = 8; /** - * A wrapper around HardwareBuffer that contains extra information about how to - * interpret the screenshot HardwareBuffer. - * - * @hide - */ - public static class ScreenshotHardwareBuffer { - private final HardwareBuffer mHardwareBuffer; - private final ColorSpace mColorSpace; - private final boolean mContainsSecureLayers; - private final boolean mContainsHdrLayers; - - public ScreenshotHardwareBuffer(HardwareBuffer hardwareBuffer, ColorSpace colorSpace, - boolean containsSecureLayers, boolean containsHdrLayers) { - mHardwareBuffer = hardwareBuffer; - mColorSpace = colorSpace; - mContainsSecureLayers = containsSecureLayers; - mContainsHdrLayers = containsHdrLayers; - } - - /** - * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. - * @param hardwareBuffer The existing HardwareBuffer object - * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} - * @param containsSecureLayers Indicates whether this graphic buffer contains captured - * contents of secure layers, in which case the screenshot - * should not be persisted. - * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. - */ - private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer, - int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) { - ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]); - return new ScreenshotHardwareBuffer( - hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers); - } - - public ColorSpace getColorSpace() { - return mColorSpace; - } - - public HardwareBuffer getHardwareBuffer() { - return mHardwareBuffer; - } - - public boolean containsSecureLayers() { - return mContainsSecureLayers; - } - /** - * Returns whether the screenshot contains at least one HDR layer. - * This information may be useful for informing the display whether this screenshot - * is allowed to be dimmed to SDR white. - */ - public boolean containsHdrLayers() { - return mContainsHdrLayers; - } - - /** - * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it. - * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap - * into - * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)} - * - * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to - * directly - * use the {@link HardwareBuffer} directly. - * - * @return Bitmap generated from the {@link HardwareBuffer} - */ - public Bitmap asBitmap() { - if (mHardwareBuffer == null) { - Log.w(TAG, "Failed to take screenshot. Null screenshot object"); - return null; - } - return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace); - } - } - - /** - * @hide - */ - public interface ScreenCaptureListener { - /** - * The callback invoked when the screen capture is complete. - * @param hardwareBuffer Data containing info about the screen capture. - */ - void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer); - } - - private static class SyncScreenCaptureListener implements ScreenCaptureListener { - private static final int SCREENSHOT_WAIT_TIME_S = 1; - private ScreenshotHardwareBuffer mScreenshotHardwareBuffer; - private final CountDownLatch mCountDownLatch = new CountDownLatch(1); - - @Override - public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) { - mScreenshotHardwareBuffer = hardwareBuffer; - mCountDownLatch.countDown(); - } - - private ScreenshotHardwareBuffer waitForScreenshot() { - try { - mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); - } catch (Exception e) { - Log.e(TAG, "Failed to wait for screen capture result", e); - } - - return mScreenshotHardwareBuffer; - } - } - - /** - * A common arguments class used for various screenshot requests. This contains arguments that - * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs} - * @hide - */ - private abstract static class CaptureArgs { - private final int mPixelFormat; - private final Rect mSourceCrop = new Rect(); - private final float mFrameScaleX; - private final float mFrameScaleY; - private final boolean mCaptureSecureLayers; - private final boolean mAllowProtected; - private final long mUid; - private final boolean mGrayscale; - - private CaptureArgs(Builder<? extends Builder<?>> builder) { - mPixelFormat = builder.mPixelFormat; - mSourceCrop.set(builder.mSourceCrop); - mFrameScaleX = builder.mFrameScaleX; - mFrameScaleY = builder.mFrameScaleY; - mCaptureSecureLayers = builder.mCaptureSecureLayers; - mAllowProtected = builder.mAllowProtected; - mUid = builder.mUid; - mGrayscale = builder.mGrayscale; - } - - /** - * The Builder class used to construct {@link CaptureArgs} - * - * @param <T> A builder that extends {@link Builder} - */ - abstract static class Builder<T extends Builder<T>> { - private int mPixelFormat = PixelFormat.RGBA_8888; - private final Rect mSourceCrop = new Rect(); - private float mFrameScaleX = 1; - private float mFrameScaleY = 1; - private boolean mCaptureSecureLayers; - private boolean mAllowProtected; - private long mUid = -1; - private boolean mGrayscale; - - /** - * The desired pixel format of the returned buffer. - */ - public T setPixelFormat(int pixelFormat) { - mPixelFormat = pixelFormat; - return getThis(); - } - - /** - * The portion of the screen to capture into the buffer. Caller may pass in - * 'new Rect()' or null if no cropping is desired. - */ - public T setSourceCrop(@Nullable Rect sourceCrop) { - if (sourceCrop == null) { - mSourceCrop.setEmpty(); - } else { - mSourceCrop.set(sourceCrop); - } - return getThis(); - } - - /** - * The desired scale of the returned buffer. The raw screen will be scaled up/down. - */ - public T setFrameScale(float frameScale) { - mFrameScaleX = frameScale; - mFrameScaleY = frameScale; - return getThis(); - } - - /** - * The desired scale of the returned buffer, allowing separate values for x and y scale. - * The raw screen will be scaled up/down. - */ - public T setFrameScale(float frameScaleX, float frameScaleY) { - mFrameScaleX = frameScaleX; - mFrameScaleY = frameScaleY; - return getThis(); - } - - /** - * Whether to allow the screenshot of secure layers. Warning: This should only be done - * if the content will be placed in a secure SurfaceControl. - * - * @see ScreenshotHardwareBuffer#containsSecureLayers() - */ - public T setCaptureSecureLayers(boolean captureSecureLayers) { - mCaptureSecureLayers = captureSecureLayers; - return getThis(); - } - - /** - * Whether to allow the screenshot of protected (DRM) content. Warning: The screenshot - * cannot be read in unprotected space. - * - * @see HardwareBuffer#USAGE_PROTECTED_CONTENT - */ - public T setAllowProtected(boolean allowProtected) { - mAllowProtected = allowProtected; - return getThis(); - } - - /** - * Set the uid of the content that should be screenshot. The code will skip any surfaces - * that don't belong to the specified uid. - */ - public T setUid(long uid) { - mUid = uid; - return getThis(); - } - - /** - * Set whether the screenshot should use grayscale or not. - */ - public T setGrayscale(boolean grayscale) { - mGrayscale = grayscale; - return getThis(); - } - - /** - * Each sub class should return itself to allow the builder to chain properly - */ - abstract T getThis(); - } - } - - /** - * The arguments class used to make display capture requests. - * - * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener) - * @hide - */ - public static class DisplayCaptureArgs extends CaptureArgs { - private final IBinder mDisplayToken; - private final int mWidth; - private final int mHeight; - private final boolean mUseIdentityTransform; - - private DisplayCaptureArgs(Builder builder) { - super(builder); - mDisplayToken = builder.mDisplayToken; - mWidth = builder.mWidth; - mHeight = builder.mHeight; - mUseIdentityTransform = builder.mUseIdentityTransform; - } - - /** - * The Builder class used to construct {@link DisplayCaptureArgs} - */ - public static class Builder extends CaptureArgs.Builder<Builder> { - private IBinder mDisplayToken; - private int mWidth; - private int mHeight; - private boolean mUseIdentityTransform; - - /** - * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder - * remains valid. - */ - public DisplayCaptureArgs build() { - if (mDisplayToken == null) { - throw new IllegalStateException( - "Can't take screenshot with null display token"); - } - return new DisplayCaptureArgs(this); - } - - public Builder(IBinder displayToken) { - setDisplayToken(displayToken); - } - - /** - * The display to take the screenshot of. - */ - public Builder setDisplayToken(IBinder displayToken) { - mDisplayToken = displayToken; - return this; - } - - /** - * Set the desired size of the returned buffer. The raw screen will be scaled down to - * this size - * - * @param width The desired width of the returned buffer. Caller may pass in 0 if no - * scaling is desired. - * @param height The desired height of the returned buffer. Caller may pass in 0 if no - * scaling is desired. - */ - public Builder setSize(int width, int height) { - mWidth = width; - mHeight = height; - return this; - } - - /** - * Replace the rotation transform of the display with the identity transformation while - * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0 - * orientation. Set this value to false if the screenshot should be taken in the - * current screen orientation. - */ - public Builder setUseIdentityTransform(boolean useIdentityTransform) { - mUseIdentityTransform = useIdentityTransform; - return this; - } - - @Override - Builder getThis() { - return this; - } - } - } - - /** - * The arguments class used to make layer capture requests. - * - * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener) - * @hide - */ - public static class LayerCaptureArgs extends CaptureArgs { - private final long mNativeLayer; - private final long[] mNativeExcludeLayers; - private final boolean mChildrenOnly; - - private LayerCaptureArgs(Builder builder) { - super(builder); - mChildrenOnly = builder.mChildrenOnly; - mNativeLayer = builder.mLayer.mNativeObject; - if (builder.mExcludeLayers != null) { - mNativeExcludeLayers = new long[builder.mExcludeLayers.length]; - for (int i = 0; i < builder.mExcludeLayers.length; i++) { - mNativeExcludeLayers[i] = builder.mExcludeLayers[i].mNativeObject; - } - } else { - mNativeExcludeLayers = null; - } - } - - /** - * The Builder class used to construct {@link LayerCaptureArgs} - */ - public static class Builder extends CaptureArgs.Builder<Builder> { - private SurfaceControl mLayer; - private SurfaceControl[] mExcludeLayers; - private boolean mChildrenOnly = true; - - /** - * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder - * remains valid. - */ - public LayerCaptureArgs build() { - if (mLayer == null) { - throw new IllegalStateException( - "Can't take screenshot with null layer"); - } - return new LayerCaptureArgs(this); - } - - public Builder(SurfaceControl layer) { - setLayer(layer); - } - - /** - * The root layer to capture. - */ - public Builder setLayer(SurfaceControl layer) { - mLayer = layer; - return this; - } - - - /** - * An array of layer handles to exclude. - */ - public Builder setExcludeLayers(@Nullable SurfaceControl[] excludeLayers) { - mExcludeLayers = excludeLayers; - return this; - } - - /** - * Whether to include the layer itself in the screenshot or just the children and their - * descendants. - */ - public Builder setChildrenOnly(boolean childrenOnly) { - mChildrenOnly = childrenOnly; - return this; - } - - @Override - Builder getThis() { - return this; - } - - } - } - - /** * Builder class for {@link SurfaceControl} objects. * * By default the surface will be hidden, and have "unset" bounds, meaning it can @@ -2355,179 +1951,130 @@ public final class SurfaceControl implements Parcelable { } /** - * Overrides HDR modes for a display device. + * Because this API is now going through {@link DisplayManager}, orientation and displayRect + * will automatically be computed based on configuration changes. Because of this, the params + * orientation and displayRect are ignored * - * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security - * Exception. * @hide */ - @TestApi - public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) { - nativeOverrideHdrTypes(displayToken, modes); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.", + trackingBug = 247078497) + public static void setDisplayProjection(IBinder displayToken, int orientation, + Rect layerStackRect, Rect displayRect) { + DisplayManagerGlobal.getInstance().resizeVirtualDisplay( + IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(), + layerStackRect.height(), 1); } /** * @hide */ - @UnsupportedAppUsage - public static IBinder createDisplay(String name, boolean secure) { - if (name == null) { - throw new IllegalArgumentException("name must not be null"); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag " + + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.", + trackingBug = 247078497) + public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { + IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); + if (b == null) { + throw new UnsupportedOperationException(); } - return nativeCreateDisplay(name, secure); - } - /** - * @hide - */ - @UnsupportedAppUsage - public static void destroyDisplay(IBinder displayToken) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); + IDisplayManager dm = IDisplayManager.Stub.asInterface(b); + try { + dm.setDisplayIdToMirror(displayToken, layerStack); + } catch (RemoteException e) { + throw new UnsupportedOperationException(e); } - nativeDestroyDisplay(displayToken); } /** * @hide */ - public static long[] getPhysicalDisplayIds() { - return nativeGetPhysicalDisplayIds(); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.", + trackingBug = 247078497) + public static void setDisplaySurface(IBinder displayToken, Surface surface) { + IVirtualDisplayCallback virtualDisplayCallback = + IVirtualDisplayCallback.Stub.asInterface(displayToken); + DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); + dm.setVirtualDisplaySurface(virtualDisplayCallback, surface); } /** + * Secure is no longer supported because this is only called from outside system which cannot + * create secure displays. * @hide */ - public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { - return nativeGetPhysicalDisplayToken(physicalDisplayId); - } - - /** - * TODO(b/116025192): Remove this stopgap once framework is display-agnostic. - * - * @hide - */ - @TestApi - @NonNull - public static IBinder getInternalDisplayToken() { - final long[] physicalDisplayIds = getPhysicalDisplayIds(); - if (physicalDisplayIds.length == 0) { - return null; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or " + + "{@code DisplayManager#createVirtualDisplay()} instead.", + trackingBug = 247078497) + public static IBinder createDisplay(String name, boolean secure) { + if (name == null) { + throw new IllegalArgumentException("name must not be null"); } - return getPhysicalDisplayToken(physicalDisplayIds[0]); - } - /** - * @param captureArgs Arguments about how to take the screenshot - * @param captureListener A listener to receive the screenshot callback - * @hide - */ - public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs, - @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureDisplay(captureArgs, captureListener); + // We don't have a size yet so pass in 1 for width and height since 0 is invalid + VirtualDisplay vd = MediaProjectionGlobal.getInstance().createVirtualDisplay(name, + 1 /* width */, 1 /* height */, INVALID_DISPLAY, null /* Surface */); + return vd == null ? null : vd.getToken().asBinder(); } /** - * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with - * the content. - * * @hide */ - public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) { - SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); - - int status = captureDisplay(captureArgs, screenCaptureListener); - if (status != 0) { - return null; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code VirtualDisplay#release()} instead.", + trackingBug = 247078497) + public static void destroyDisplay(IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); } - return screenCaptureListener.waitForScreenshot(); + DisplayManagerGlobal.getInstance().releaseVirtualDisplay( + IVirtualDisplayCallback.Stub.asInterface(displayToken)); } /** - * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. - * - * @param layer The root layer to capture. - * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new - * Rect()' or null if no cropping is desired. If the root layer does not - * have a buffer or a crop set, then a non-empty source crop must be - * specified. - * @param frameScale The desired scale of the returned buffer; the raw - * screen will be scaled up/down. - * - * @return Returns a HardwareBuffer that contains the layer capture. - * @hide - */ - public static ScreenshotHardwareBuffer captureLayers(SurfaceControl layer, Rect sourceCrop, - float frameScale) { - return captureLayers(layer, sourceCrop, frameScale, PixelFormat.RGBA_8888); - } - - /** - * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. - * - * @param layer The root layer to capture. - * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new - * Rect()' or null if no cropping is desired. If the root layer does not - * have a buffer or a crop set, then a non-empty source crop must be - * specified. - * @param frameScale The desired scale of the returned buffer; the raw - * screen will be scaled up/down. - * @param format The desired pixel format of the returned buffer. + * Overrides HDR modes for a display device. * - * @return Returns a HardwareBuffer that contains the layer capture. + * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security + * Exception. * @hide */ - public static ScreenshotHardwareBuffer captureLayers(@NonNull SurfaceControl layer, - @Nullable Rect sourceCrop, float frameScale, int format) { - LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer) - .setSourceCrop(sourceCrop) - .setFrameScale(frameScale) - .setPixelFormat(format) - .build(); - - return captureLayers(captureArgs); + @TestApi + public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) { + nativeOverrideHdrTypes(displayToken, modes); } /** * @hide */ - public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) { - SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); - - int status = captureLayers(captureArgs, screenCaptureListener); - if (status != 0) { - return null; - } - - return screenCaptureListener.waitForScreenshot(); + public static long[] getPhysicalDisplayIds() { + return nativeGetPhysicalDisplayIds(); } /** - * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer - * handles to exclude. * @hide */ - public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer, - Rect sourceCrop, float frameScale, int format, SurfaceControl[] exclude) { - LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer) - .setSourceCrop(sourceCrop) - .setFrameScale(frameScale) - .setPixelFormat(format) - .setExcludeLayers(exclude) - .build(); - - return captureLayers(captureArgs); + public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { + return nativeGetPhysicalDisplayToken(physicalDisplayId); } /** - * @param captureArgs Arguments about how to take the screenshot - * @param captureListener A listener to receive the screenshot callback + * TODO(b/116025192): Remove this stopgap once framework is display-agnostic. + * * @hide */ - public static int captureLayers(@NonNull LayerCaptureArgs captureArgs, - @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureLayers(captureArgs, captureListener); + @TestApi + @NonNull + public static IBinder getInternalDisplayToken() { + final long[] physicalDisplayIds = getPhysicalDisplayIds(); + if (physicalDisplayIds.length == 0) { + return null; + } + return getPhysicalDisplayToken(physicalDisplayIds[0]); } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 7acf319a5049..9075de13c4d8 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -107,6 +107,14 @@ import java.util.function.Consumer; * and scaling a SurfaceView on screen will not cause rendering artifacts. Such * artifacts may occur on previous versions of the platform when its window is * positioned asynchronously.</p> + * + * <p class="note"><strong>Note:</strong> Starting in platform version + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, SurfaceView will support arbitrary + * alpha blending. Prior platform versions ignored alpha values on the SurfaceView if they were + * between 0 and 1. If the SurfaceView is configured with Z-above, then the alpha is applied + * directly to the Surface. If the SurfaceView is configured with Z-below, then the alpha is + * applied to the hole punch directly. Note that when using Z-below, overlapping SurfaceViews + * may not blend properly as a consequence of not applying alpha to the surface content directly. */ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCallback { private static final String TAG = "SurfaceView"; @@ -146,6 +154,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall Paint mRoundedViewportPaint; int mSubLayer = APPLICATION_MEDIA_SUBLAYER; + int mRequestedSubLayer = APPLICATION_MEDIA_SUBLAYER; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) boolean mIsCreating = false; @@ -177,8 +186,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @UnsupportedAppUsage int mRequestedFormat = PixelFormat.RGB_565; - boolean mUseAlpha = false; - float mSurfaceAlpha = 1f; + float mAlpha = 1f; boolean mClipSurfaceToBounds; int mBackgroundColor = Color.BLACK; @@ -335,58 +343,25 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @hide */ public void setUseAlpha() { - if (!mUseAlpha) { - mUseAlpha = true; - updateSurfaceAlpha(); - } + // TODO(b/241474646): Remove me + return; } @Override public void setAlpha(float alpha) { - // Sets the opacity of the view to a value, where 0 means the view is completely transparent - // and 1 means the view is completely opaque. - // - // Note: Alpha value of this view is ignored by default. To enable alpha blending, you need - // to call setUseAlpha() as well. - // This view doesn't support translucent opacity if the view is located z-below, since the - // logic to punch a hole in the view hierarchy cannot handle such case. See also - // #clearSurfaceViewPort(Canvas) if (DEBUG) { Log.d(TAG, System.identityHashCode(this) - + " setAlpha: mUseAlpha = " + mUseAlpha + " alpha=" + alpha); + + " setAlpha: alpha=" + alpha); } super.setAlpha(alpha); - updateSurfaceAlpha(); - } - - private float getFixedAlpha() { - // Compute alpha value to be set on the underlying surface. - final float alpha = getAlpha(); - return mUseAlpha && (mSubLayer > 0 || alpha == 0f) ? alpha : 1f; } - private void updateSurfaceAlpha() { - if (!mUseAlpha || !mHaveFrame || mSurfaceControl == null) { - return; - } - final float viewAlpha = getAlpha(); - if (mSubLayer < 0 && 0f < viewAlpha && viewAlpha < 1f) { - Log.w(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha:" - + " translucent color is not supported for a surface placed z-below."); - } - final ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot == null) { - return; - } - final float alpha = getFixedAlpha(); - if (alpha != mSurfaceAlpha) { - final Transaction transaction = new Transaction(); - transaction.setAlpha(mSurfaceControl, alpha); - viewRoot.applyTransactionOnDraw(transaction); - damageInParent(); - mSurfaceAlpha = alpha; + @Override + protected boolean onSetAlpha(int alpha) { + if (Math.round(mAlpha * 255) != alpha) { + updateSurface(); } + return true; } private void performDrawFinished() { @@ -534,7 +509,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall invalidate(); } + @Override + public boolean hasOverlappingRendering() { + // SurfaceViews only alpha composite by modulating the Surface alpha for Z-above, or + // applying alpha on the hole punch for Z-below - no deferral to a layer is necessary. + return false; + } + private void clearSurfaceViewPort(Canvas canvas) { + final float alpha = getAlpha(); if (mCornerRadius > 0f) { canvas.getClipBounds(mTmpRect); if (mClipSurfaceToBounds && mClipBounds != null) { @@ -546,10 +529,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mTmpRect.right, mTmpRect.bottom, mCornerRadius, - mCornerRadius + mCornerRadius, + alpha ); } else { - canvas.punchHole(0f, 0f, getWidth(), getHeight(), 0f, 0f); + canvas.punchHole(0f, 0f, getWidth(), getHeight(), 0f, 0f, alpha); } } @@ -626,7 +610,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @hide */ public boolean isZOrderedOnTop() { - return mSubLayer > 0; + return mRequestedSubLayer > 0; } /** @@ -652,10 +636,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } else { subLayer = APPLICATION_MEDIA_SUBLAYER; } - if (mSubLayer == subLayer) { + if (mRequestedSubLayer == subLayer) { return false; } - mSubLayer = subLayer; + mRequestedSubLayer = subLayer; if (!allowDynamicChange) { return false; @@ -667,9 +651,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (viewRoot == null) { return true; } - final Transaction transaction = new SurfaceControl.Transaction(); - updateRelativeZ(transaction); - viewRoot.applyTransactionOnDraw(transaction); + + updateSurface(); invalidate(); return true; } @@ -722,8 +705,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void releaseSurfaces(boolean releaseSurfacePackage) { - mSurfaceAlpha = 1f; - + mAlpha = 1f; synchronized (mSurfaceControlLock) { mSurface.destroy(); if (mBlastBufferQueue != null) { @@ -770,7 +752,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator, - boolean creating, boolean sizeChanged, boolean hintChanged, + boolean creating, boolean sizeChanged, boolean hintChanged, boolean relativeZChanged, Transaction surfaceUpdateTransaction) { boolean realSizeChanged = false; @@ -800,14 +782,20 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall surfaceUpdateTransaction.hide(mSurfaceControl); } - - updateBackgroundVisibility(surfaceUpdateTransaction); updateBackgroundColor(surfaceUpdateTransaction); - if (mUseAlpha) { - float alpha = getFixedAlpha(); + if (isAboveParent()) { + float alpha = getAlpha(); surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha); - mSurfaceAlpha = alpha; + } + + if (relativeZChanged) { + if (!isAboveParent()) { + // If we're moving from z-above to z-below, then restore the surface alpha back to 1 + // and let the holepunch drive visibility and blending. + surfaceUpdateTransaction.setAlpha(mSurfaceControl, 1.f); + } + updateRelativeZ(surfaceUpdateTransaction); } surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius); @@ -873,6 +861,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } finally { mSurfaceLock.unlock(); } + return realSizeChanged; } @@ -906,10 +895,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall int myHeight = mRequestedHeight; if (myHeight <= 0) myHeight = getHeight(); - final float alpha = getFixedAlpha(); + final float alpha = getAlpha(); final boolean formatChanged = mFormat != mRequestedFormat; final boolean visibleChanged = mVisible != mRequestedVisible; - final boolean alphaChanged = mSurfaceAlpha != alpha; + final boolean alphaChanged = mAlpha != alpha; final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged) && mRequestedVisible; final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight; @@ -921,17 +910,17 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall || getHeight() != mScreenRect.height(); final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint) && mRequestedVisible; + final boolean relativeZChanged = mSubLayer != mRequestedSubLayer; - if (creating || formatChanged || sizeChanged || visibleChanged || - (mUseAlpha && alphaChanged) || windowVisibleChanged || - positionChanged || layoutSizeChanged || hintChanged) { + if (creating || formatChanged || sizeChanged || visibleChanged + || alphaChanged || windowVisibleChanged || positionChanged + || layoutSizeChanged || hintChanged || relativeZChanged) { if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Changes: creating=" + creating + " format=" + formatChanged + " size=" + sizeChanged + " visible=" + visibleChanged + " alpha=" + alphaChanged + " hint=" + hintChanged - + " mUseAlpha=" + mUseAlpha + " visible=" + visibleChanged + " left=" + (mWindowSpaceLeft != mLocation[0]) + " top=" + (mWindowSpaceTop != mLocation[1])); @@ -943,8 +932,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceWidth = myWidth; mSurfaceHeight = myHeight; mFormat = mRequestedFormat; + mAlpha = alpha; mLastWindowVisibility = mWindowVisibility; mTransformHint = viewRoot.getBufferTransformHint(); + mSubLayer = mRequestedSubLayer; mScreenRect.left = mWindowSpaceLeft; mScreenRect.top = mWindowSpaceTop; @@ -968,7 +959,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } final boolean redrawNeeded = sizeChanged || creating || hintChanged - || (mVisible && !mDrawFinished); + || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged; boolean shouldSyncBuffer = redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync(); SyncBufferTransactionCallback syncBufferTransactionCallback = null; @@ -979,8 +970,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall syncBufferTransactionCallback::onTransactionReady); } - final boolean realSizeChanged = performSurfaceTransaction(viewRoot, - translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction); + final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator, + creating, sizeChanged, hintChanged, relativeZChanged, + surfaceUpdateTransaction); try { SurfaceHolder.Callback[] callbacks = null; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8fee4db458b3..15aa4b44ac26 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3095,7 +3095,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p> * Accessibility interactions from services without {@code isAccessibilityTool} set to true are * disallowed for any of the following conditions: - * <li>this view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.</li> * <li>this view sets {@link #getFilterTouchesWhenObscured()}.</li> * <li>any parent of this view returns true from {@link #isAccessibilityDataPrivate()}.</li> * </p> @@ -8326,10 +8325,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Visually distinct portion of a window with window-like semantics are considered panes for - * accessibility purposes. One example is the content view of a fragment that is replaced. + * accessibility purposes. One example is the content view of a large fragment that is replaced. * In order for accessibility services to understand a pane's window-like behavior, panes - * should have descriptive titles. Views with pane titles produce {@link AccessibilityEvent}s - * when they appear, disappear, or change title. + * should have descriptive titles. Views with pane titles produce + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}s when they appear, disappear, or change + * title. + * + * <p> + * When transitioning from one Activity to another, instead of using + * setAccessibilityPaneTitle(), set a descriptive title for its window by using android:label + * for the matching <activity> entry in your application’s manifest or updating the title at + * runtime with{@link android.app.Activity#setTitle(CharSequence)}. * * @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this * View is not a pane. @@ -10995,8 +11001,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * description. An image of a floppy disk that is used to save a file may * use "Save". * + * <p> + * This should omit role or state. Role refers to the kind of user-interface element the View + * is, such as a Button or Checkbox. State refers to a frequently changing property of the View, + * such as an On/Off state of a button or the audio level of a volume slider. + * + * <p> + * Content description updates are not frequent, and are used when the semantic content - not + * the state - of the element changes. For example, a Play button might change to a Pause + * button during music playback. + * * @param contentDescription The content description. * @see #getContentDescription() + * @see #setStateDescription(CharSequence)} for state changes. * @attr ref android.R.styleable#View_contentDescription */ @RemotableViewMethod @@ -13897,6 +13914,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * See also {@link #focusSearch(int)}, which is what you call to say that you * have focus, and you want your parent to look for the next one. * + * <p> + * <b>Note:</b> Avoid setting accessibility focus. This is intended to be controlled by screen + * readers. Apps changing focus can confuse screen readers, so the resulting behavior can vary + * by device and screen reader version. + * * @return Whether this view actually took accessibility focus. * * @hide @@ -14508,9 +14530,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Use the explicit value if set. if (mExplicitAccessibilityDataPrivate != ACCESSIBILITY_DATA_PRIVATE_AUTO) { mInferredAccessibilityDataPrivate = mExplicitAccessibilityDataPrivate; - } else if (mAttachInfo != null && mAttachInfo.mWindowSecure) { - // Views inside FLAG_SECURE windows default to accessibilityDataPrivate. - mInferredAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_YES; } else if (getFilterTouchesWhenObscured()) { // Views that set filterTouchesWhenObscured default to accessibilityDataPrivate. mInferredAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_YES; @@ -14735,6 +14754,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if * {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.</p> * + * <p> + * <b>Note:</b> Avoid setting accessibility focus with + * {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}. This is intended to be controlled + * by screen readers. Apps changing focus can confuse screen readers, so the resulting behavior + * can vary by device and screen reader version. + * * @param action The action to perform. * @param arguments Optional action arguments. * @return Whether the action was performed. @@ -24490,8 +24515,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Set the current default focus highlight. * @param highlight the highlight drawable, or {@code null} if it's no longer needed. + * @hide */ - private void setDefaultFocusHighlight(Drawable highlight) { + void setDefaultFocusHighlight(Drawable highlight) { mDefaultFocusHighlight = highlight; mDefaultFocusHighlightSizeChanged = true; if (highlight != null) { @@ -30232,11 +30258,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mWindowVisibility; /** - * Indicates whether the view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}. - */ - boolean mWindowSecure; - - /** * Indicates the time at which drawing started to occur. */ @UnsupportedAppUsage diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 074cbe5a6947..4fca4cf17d90 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2838,7 +2838,6 @@ public final class ViewRootImpl implements ViewParent, // However, windows are now always 32 bits by default, so choose 32 bits mAttachInfo.mUse32BitDrawingCache = true; mAttachInfo.mWindowVisibility = viewVisibility; - mAttachInfo.mWindowSecure = (lp.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0; mAttachInfo.mRecomputeGlobalAttributes = false; mLastConfigurationFromResources.setTo(config); mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; @@ -3099,7 +3098,8 @@ public final class ViewRootImpl implements ViewParent, // WindowManagerService has reported back a frame from a configuration not yet // handled by the client. In this case, we need to accept the configuration so we // do not lay out and draw with the wrong configuration. - if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { + if (mRelayoutRequested + && !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: " + mPendingMergedConfiguration.getMergedConfiguration()); performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration), @@ -3815,6 +3815,13 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mTooltipHost != null) { mAttachInfo.mTooltipHost.hideTooltip(); } + if (!hasWindowFocus) { + // Clear focus highlight if its window lost focus. + final View focused = mView.findFocus(); + if (focused != null) { + focused.setDefaultFocusHighlight(null); + } + } } // Note: must be done after the focus change callbacks, @@ -5846,7 +5853,13 @@ public final class ViewRootImpl implements ViewParent, // be when the window is first being added, and mFocused isn't // set yet. final View focused = mView.findFocus(); - if (focused != null && !focused.isFocusableInTouchMode()) { + if (focused == null) { + return false; + } + + // Clear default focus highlight if it entered touch mode. + focused.setDefaultFocusHighlight(null); + if (!focused.isFocusableInTouchMode()) { final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused); if (ancestorToTakeFocus != null) { // there is an ancestor that wants focus after its @@ -8146,7 +8159,8 @@ public final class ViewRootImpl implements ViewParent, final int measuredWidth = mView.getMeasuredWidth(); final int measuredHeight = mView.getMeasuredHeight(); final boolean relayoutAsync; - if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility + if (LOCAL_LAYOUT + && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0 && mWindowAttributes.type != TYPE_APPLICATION_STARTING && mSyncSeqId <= mLastSyncSeqId && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) { @@ -8213,6 +8227,7 @@ public final class ViewRootImpl implements ViewParent, mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls, mRelayoutBundle); mRelayoutRequested = true; + final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid"); if (maybeSyncSeqId > 0) { mSyncSeqId = maybeSyncSeqId; @@ -8253,6 +8268,12 @@ public final class ViewRootImpl implements ViewParent, } } + if (mSurfaceControl.isValid() && !HardwareRenderer.isDrawingEnabled()) { + // When drawing is disabled the window layer won't have a valid buffer. + // Set a window crop so input can get delivered to the window. + mTransaction.setWindowCrop(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y).apply(); + } + mLastTransformHint = transformHint; mSurfaceControl.setTransformHint(transformHint); diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index e89f836aaac1..7528e2a66926 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -585,6 +585,18 @@ public final class AccessibilityManager { /** * Returns if the accessibility in the system is enabled. + * <p> + * <b>Note:</b> This query is used for sending {@link AccessibilityEvent}s, since events are + * only needed if accessibility is on. Avoid changing UI or app behavior based on the state of + * accessibility. While well-intentioned, doing this creates brittle, less + * well-maintained code that works for some users but not others. Shared code leads to more + * equitable experiences and less technical debt. + * + *<p> + * For example, if you want to expose a unique interaction with your app, use + * ViewCompat#addAccessibilityAction in AndroidX to make this interaction - ideally + * with the same code path used for non-accessibility users - available to accessibility + * services. Services can then expose this action in the way best fit for their users. * * @return True if accessibility is enabled, false otherwise. */ @@ -597,6 +609,13 @@ public final class AccessibilityManager { /** * Returns if the touch exploration in the system is enabled. + * <p> + * <b>Note:</b> This query is used for dispatching hover events, such as + * {@link android.view.MotionEvent#ACTION_HOVER_ENTER}, to accessibility services to manage + * touch exploration. Avoid changing UI or app behavior based on the state of accessibility. + * While well-intentioned, doing this creates brittle, less well-maintained code that works for + * som users but not others. Shared code leads to more equitable experiences and less technical + * debt. * * @return True if touch exploration is enabled, false otherwise. */ diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 1f33806e8dd7..d07a7977b2ab 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -4829,6 +4829,9 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Action that gives accessibility focus to the node. + * <p> + * This is intended to be used by screen readers. Apps changing focus can confuse screen + * readers, so the resulting behavior can vary by device and screen reader version. */ public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS = new AccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 624937ff1d99..8d7507295e59 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -20,6 +20,7 @@ import static android.view.ContentInfo.SOURCE_INPUT_METHOD; import android.annotation.CallSuper; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ClipData; import android.content.ClipDescription; @@ -61,8 +62,15 @@ public class BaseInputConnection implements InputConnection { static final Object COMPOSING = new ComposingText(); /** @hide */ - protected final InputMethodManager mIMM; - final View mTargetView; + @NonNull protected final InputMethodManager mIMM; + + /** + * Target view for the input connection. + * + * <p>This could be null for a fallback input connection. + */ + @Nullable final View mTargetView; + final boolean mFallbackMode; private Object[] mDefaultComposingSpans; @@ -70,20 +78,25 @@ public class BaseInputConnection implements InputConnection { Editable mEditable; KeyCharacterMap mKeyCharacterMap; - BaseInputConnection(InputMethodManager mgr, boolean fullEditor) { + BaseInputConnection(@NonNull InputMethodManager mgr, boolean fullEditor) { mIMM = mgr; mTargetView = null; mFallbackMode = !fullEditor; } - public BaseInputConnection(View targetView, boolean fullEditor) { + public BaseInputConnection(@NonNull View targetView, boolean fullEditor) { mIMM = (InputMethodManager)targetView.getContext().getSystemService( Context.INPUT_METHOD_SERVICE); mTargetView = targetView; mFallbackMode = !fullEditor; } - public static final void removeComposingSpans(Spannable text) { + /** + * Removes the composing spans from the given text if any. + * + * @param text the spannable text to remove composing spans + */ + public static final void removeComposingSpans(@NonNull Spannable text) { text.removeSpan(COMPOSING); Object[] sps = text.getSpans(0, text.length(), Object.class); if (sps != null) { @@ -96,12 +109,17 @@ public class BaseInputConnection implements InputConnection { } } - public static void setComposingSpans(Spannable text) { + /** + * Removes the composing spans from the given text if any. + * + * @param text the spannable text to remove composing spans + */ + public static void setComposingSpans(@NonNull Spannable text) { setComposingSpans(text, 0, text.length()); } /** @hide */ - public static void setComposingSpans(Spannable text, int start, int end) { + public static void setComposingSpans(@NonNull Spannable text, int start, int end) { final Object[] sps = text.getSpans(start, end, Object.class); if (sps != null) { for (int i=sps.length-1; i>=0; i--) { @@ -114,7 +132,10 @@ public class BaseInputConnection implements InputConnection { final int fl = text.getSpanFlags(o); if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK)) != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { - text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), + text.setSpan( + o, + text.getSpanStart(o), + text.getSpanEnd(o), (fl & ~Spanned.SPAN_POINT_MARK_MASK) | Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -126,20 +147,24 @@ public class BaseInputConnection implements InputConnection { Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); } - public static int getComposingSpanStart(Spannable text) { + /** Return the beginning of the range of composing text, or -1 if there's no composing text. */ + public static int getComposingSpanStart(@NonNull Spannable text) { return text.getSpanStart(COMPOSING); } - public static int getComposingSpanEnd(Spannable text) { + /** Return the end of the range of composing text, or -1 if there's no composing text. */ + public static int getComposingSpanEnd(@NonNull Spannable text) { return text.getSpanEnd(COMPOSING); } /** - * Return the target of edit operations. The default implementation - * returns its own fake editable that is just used for composing text; - * subclasses that are real text editors should override this and - * supply their own. + * Return the target of edit operations. The default implementation returns its own fake + * editable that is just used for composing text; subclasses that are real text editors should + * override this and supply their own. + * + * <p>Subclasses could override this method to turn null. */ + @Nullable public Editable getEditable() { if (mEditable == null) { mEditable = Editable.Factory.getInstance().newEditable(""); @@ -148,16 +173,14 @@ public class BaseInputConnection implements InputConnection { return mEditable; } - /** - * Default implementation does nothing. - */ + /** Default implementation does nothing. */ + @Override public boolean beginBatchEdit() { return false; } - /** - * Default implementation does nothing. - */ + /** Default implementation does nothing. */ + @Override public boolean endBatchEdit() { return false; } @@ -165,29 +188,29 @@ public class BaseInputConnection implements InputConnection { /** * Called after only the composing region is modified (so it isn't called if the text also * changes). - * <p> - * Default implementation does nothing. + * + * <p>Default implementation does nothing. * * @hide */ - public void endComposingRegionEditInternal() { - } + public void endComposingRegionEditInternal() {} /** - * Default implementation calls {@link #finishComposingText()} and - * {@code setImeConsumesInput(false)}. + * Default implementation calls {@link #finishComposingText()} and {@code + * setImeConsumesInput(false)}. */ @CallSuper + @Override public void closeConnection() { finishComposingText(); setImeConsumesInput(false); } /** - * Default implementation uses - * {@link MetaKeyKeyListener#clearMetaKeyState(long, int) + * Default implementation uses {@link MetaKeyKeyListener#clearMetaKeyState(long, int) * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state. */ + @Override public boolean clearMetaKeyStates(int states) { final Editable content = getEditable(); if (content == null) return false; @@ -195,25 +218,24 @@ public class BaseInputConnection implements InputConnection { return true; } - /** - * Default implementation does nothing and returns false. - */ + /** Default implementation does nothing and returns false. */ + @Override public boolean commitCompletion(CompletionInfo text) { return false; } - /** - * Default implementation does nothing and returns false. - */ + /** Default implementation does nothing and returns false. */ + @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return false; } /** - * Default implementation replaces any existing composing text with - * the given text. In addition, only if fallback mode, a key event is - * sent for the new text and the current editable buffer cleared. + * Default implementation replaces any existing composing text with the given text. In addition, + * only if fallback mode, a key event is sent for the new text and the current editable buffer + * cleared. */ + @Override public boolean commitText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "commitText " + text); replaceText(text, newCursorPosition, false); @@ -226,21 +248,19 @@ public class BaseInputConnection implements InputConnection { * editable text. * * @param beforeLength The number of characters before the cursor to be deleted, in code unit. - * If this is greater than the number of existing characters between the beginning of the - * text and the cursor, then this method does not fail but deletes all the characters in - * that range. - * @param afterLength The number of characters after the cursor to be deleted, in code unit. - * If this is greater than the number of existing characters between the cursor and - * the end of the text, then this method does not fail but deletes all the characters in - * that range. - * - * @return {@code true} when selected text is deleted, {@code false} when either the - * selection is invalid or not yet attached (i.e. selection start or end is -1), - * or the editable text is {@code null}. + * If this is greater than the number of existing characters between the beginning of the + * text and the cursor, then this method does not fail but deletes all the characters in + * that range. + * @param afterLength The number of characters after the cursor to be deleted, in code unit. If + * this is greater than the number of existing characters between the cursor and the end of + * the text, then this method does not fail but deletes all the characters in that range. + * @return {@code true} when selected text is deleted, {@code false} when either the selection + * is invalid or not yet attached (i.e. selection start or end is -1), or the editable text + * is {@code null}. */ + @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { - if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength - + " / " + afterLength); + if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength + " / " + afterLength); final Editable content = getEditable(); if (content == null) return false; @@ -389,7 +409,7 @@ public class BaseInputConnection implements InputConnection { continue; } if (java.lang.Character.isLowSurrogate(c)) { - return INVALID_INDEX; // A invalid surrogate pair is found. + return INVALID_INDEX; // A invalid surrogate pair is found. } waitingLowSurrogate = true; ++currentIndex; @@ -399,18 +419,18 @@ public class BaseInputConnection implements InputConnection { /** * The default implementation performs the deletion around the current selection position of the * editable text. + * * @param beforeLength The number of characters before the cursor to be deleted, in code points. - * If this is greater than the number of existing characters between the beginning of the - * text and the cursor, then this method does not fail but deletes all the characters in - * that range. + * If this is greater than the number of existing characters between the beginning of the + * text and the cursor, then this method does not fail but deletes all the characters in + * that range. * @param afterLength The number of characters after the cursor to be deleted, in code points. - * If this is greater than the number of existing characters between the cursor and - * the end of the text, then this method does not fail but deletes all the characters in - * that range. + * If this is greater than the number of existing characters between the cursor and the end + * of the text, then this method does not fail but deletes all the characters in that range. */ + @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { - if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength - + " / " + afterLength); + if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength + " / " + afterLength); final Editable content = getEditable(); if (content == null) return false; @@ -466,10 +486,11 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation removes the composing state from the - * current editable text. In addition, only if fallback mode, a key event is - * sent for the new text and the current editable buffer cleared. + * The default implementation removes the composing state from the current editable text. In + * addition, only if fallback mode, a key event is sent for the new text and the current + * editable buffer cleared. */ + @Override public boolean finishComposingText() { if (DEBUG) Log.v(TAG, "finishComposingText"); final Editable content = getEditable(); @@ -485,10 +506,11 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation uses TextUtils.getCapsMode to get the - * cursor caps mode for the current selection position in the editable - * text, unless in fallback mode in which case 0 is always returned. + * The default implementation uses TextUtils.getCapsMode to get the cursor caps mode for the + * current selection position in the editable text, unless in fallback mode in which case 0 is + * always returned. */ + @Override public int getCursorCapsMode(int reqModes) { if (mFallbackMode) return 0; @@ -507,17 +529,18 @@ public class BaseInputConnection implements InputConnection { return TextUtils.getCapsMode(content, a, reqModes); } - /** - * The default implementation always returns null. - */ + /** The default implementation always returns null. */ + @Override + @Nullable public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return null; } /** - * The default implementation returns the given amount of text from the - * current cursor position in the buffer. + * The default implementation returns the given amount of text from the current cursor position + * in the buffer. */ + @Override @Nullable public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) { Preconditions.checkArgumentNonnegative(length); @@ -549,9 +572,10 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation returns the text currently selected, or null if none is - * selected. + * The default implementation returns the text currently selected, or null if none is selected. */ + @Override + @Nullable public CharSequence getSelectedText(int flags) { final Editable content = getEditable(); if (content == null) return null; @@ -574,9 +598,10 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation returns the given amount of text from the - * current cursor position in the buffer. + * The default implementation returns the given amount of text from the current cursor position + * in the buffer. */ + @Override @Nullable public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) { Preconditions.checkArgumentNonnegative(length); @@ -602,7 +627,6 @@ public class BaseInputConnection implements InputConnection { length = content.length() - b; } - if ((flags&GET_TEXT_WITH_STYLES) != 0) { return content.subSequence(b, b + length); } @@ -613,9 +637,10 @@ public class BaseInputConnection implements InputConnection { * The default implementation returns the given amount of text around the current cursor * position in the buffer. */ + @Override @Nullable public SurroundingText getSurroundingText( - @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) { + @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) { Preconditions.checkArgumentNonnegative(beforeLength); Preconditions.checkArgumentNonnegative(afterLength); @@ -659,60 +684,75 @@ public class BaseInputConnection implements InputConnection { surroundingText, selStart - startPos, selEnd - startPos, startPos); } - /** - * The default implementation turns this into the enter key. - */ + /** The default implementation turns this into the enter key. */ + @Override public boolean performEditorAction(int actionCode) { long eventTime = SystemClock.uptimeMillis(); - sendKeyEvent(new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE - | KeyEvent.FLAG_EDITOR_ACTION)); - sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, - KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE - | KeyEvent.FLAG_EDITOR_ACTION)); + sendKeyEvent( + new KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_ENTER, + 0, + 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, + 0, + KeyEvent.FLAG_SOFT_KEYBOARD + | KeyEvent.FLAG_KEEP_TOUCH_MODE + | KeyEvent.FLAG_EDITOR_ACTION)); + sendKeyEvent( + new KeyEvent( + SystemClock.uptimeMillis(), + eventTime, + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_ENTER, + 0, + 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, + 0, + KeyEvent.FLAG_SOFT_KEYBOARD + | KeyEvent.FLAG_KEEP_TOUCH_MODE + | KeyEvent.FLAG_EDITOR_ACTION)); return true; } - /** - * The default implementation does nothing. - */ + /** The default implementation does nothing. */ + @Override public boolean performContextMenuAction(int id) { return false; } - /** - * The default implementation does nothing. - */ + /** The default implementation does nothing. */ + @Override public boolean performPrivateCommand(String action, Bundle data) { return false; } - /** - * The default implementation does nothing. - */ + /** The default implementation does nothing. */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return false; } + @Override + @Nullable public Handler getHandler() { return null; } /** - * The default implementation places the given text into the editable, - * replacing any existing composing text. The new text is marked as - * in a composing state with the composing style. + * The default implementation places the given text into the editable, replacing any existing + * composing text. The new text is marked as in a composing state with the composing style. */ + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "setComposingText " + text); replaceText(text, newCursorPosition, true); return true; } + @Override public boolean setComposingRegion(int start, int end) { final Editable content = getEditable(); if (content != null) { @@ -735,7 +775,10 @@ public class BaseInputConnection implements InputConnection { ensureDefaultComposingSpans(); if (mDefaultComposingSpans != null) { for (int i = 0; i < mDefaultComposingSpans.length; ++i) { - content.setSpan(mDefaultComposingSpans[i], a, b, + content.setSpan( + mDefaultComposingSpans[i], + a, + b, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); } } @@ -751,10 +794,8 @@ public class BaseInputConnection implements InputConnection { return true; } - /** - * The default implementation changes the selection position in the - * current editable text. - */ + /** The default implementation changes the selection position in the current editable text. */ + @Override public boolean setSelection(int start, int end) { if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); final Editable content = getEditable(); @@ -779,17 +820,17 @@ public class BaseInputConnection implements InputConnection { } /** - * Provides standard implementation for sending a key event to the window - * attached to the input connection's view. + * Provides standard implementation for sending a key event to the window attached to the input + * connection's view. */ + @Override public boolean sendKeyEvent(KeyEvent event) { mIMM.dispatchKeyEventFromInputMethod(mTargetView, event); return false; } - /** - * Updates InputMethodManager with the current fullscreen mode. - */ + /** Updates InputMethodManager with the current fullscreen mode. */ + @Override public boolean reportFullscreenMode(boolean enabled) { return true; } @@ -934,8 +975,7 @@ public class BaseInputConnection implements InputConnection { newCursorPosition += a; } if (newCursorPosition < 0) newCursorPosition = 0; - if (newCursorPosition > content.length()) - newCursorPosition = content.length(); + if (newCursorPosition > content.length()) newCursorPosition = content.length(); Selection.setSelection(content, newCursorPosition); content.replace(a, b, text); @@ -950,11 +990,16 @@ public class BaseInputConnection implements InputConnection { } /** - * Default implementation which invokes {@link View#performReceiveContent} on the target - * view if the view {@link View#getReceiveContentMimeTypes allows} content insertion; - * otherwise returns false without any side effects. + * Default implementation which invokes {@link View#performReceiveContent} on the target view if + * the view {@link View#getReceiveContentMimeTypes allows} content insertion; otherwise returns + * false without any side effects. */ + @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { + if (mTargetView == null) { + return false; + } + ClipDescription description = inputContentInfo.getDescription(); if (mTargetView.getReceiveContentMimeTypes() == null) { if (DEBUG) { diff --git a/core/java/android/view/inputmethod/DeleteGesture.java b/core/java/android/view/inputmethod/DeleteGesture.java index 9fadabfee512..c88158f3b39a 100644 --- a/core/java/android/view/inputmethod/DeleteGesture.java +++ b/core/java/android/view/inputmethod/DeleteGesture.java @@ -27,9 +27,11 @@ import android.widget.TextView; import java.util.Objects; /** - * A sub-class of {@link HandwritingGesture} for deleting an area of text. + * A sub-class of {@link HandwritingGesture} for deleting an area of text using single rectangle. * This class holds the information required for deletion of text in * toolkit widgets like {@link TextView}. + * <p>Note: This deletes all text <em>within</em> the given area. To delete a range <em>between</em> + * two areas, use {@link DeleteRangeGesture}.</p> */ public final class DeleteGesture extends HandwritingGesture implements Parcelable { diff --git a/core/java/android/view/inputmethod/DeleteRangeGesture.aidl b/core/java/android/view/inputmethod/DeleteRangeGesture.aidl new file mode 100644 index 000000000000..0ae889974fc7 --- /dev/null +++ b/core/java/android/view/inputmethod/DeleteRangeGesture.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable DeleteRangeGesture;
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/DeleteRangeGesture.java b/core/java/android/view/inputmethod/DeleteRangeGesture.java new file mode 100644 index 000000000000..53b42092c181 --- /dev/null +++ b/core/java/android/view/inputmethod/DeleteRangeGesture.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.TextView; + +import java.util.Objects; + +/** + * A subclass of {@link HandwritingGesture} for deleting a range of text by defining start and end + * rectangles. This can be useful when the range cannot be defined with a single rectangle. + * This class holds the information required for deletion of text in + * toolkit widgets like {@link TextView}. + * <p>Note: this deletes text within a range <em>between</em> two given areas. To delete all text + * <em>within</em> a single area, use {@link DeleteGesture}.</p> + */ +public final class DeleteRangeGesture extends HandwritingGesture implements Parcelable { + + private @Granularity int mGranularity; + private RectF mStartArea; + private RectF mEndArea; + + private DeleteRangeGesture( + int granularity, RectF startArea, RectF endArea, String fallbackText) { + mType = GESTURE_TYPE_DELETE_RANGE; + mStartArea = startArea; + mEndArea = endArea; + mGranularity = granularity; + mFallbackText = fallbackText; + } + + private DeleteRangeGesture(@NonNull Parcel source) { + mType = GESTURE_TYPE_DELETE_RANGE; + mFallbackText = source.readString8(); + mGranularity = source.readInt(); + mStartArea = source.readTypedObject(RectF.CREATOR); + mEndArea = source.readTypedObject(RectF.CREATOR); + } + + /** + * Returns Granular level on which text should be operated. + * @see #GRANULARITY_CHARACTER + * @see #GRANULARITY_WORD + */ + @Granularity + public int getGranularity() { + return mGranularity; + } + + /** + * Returns the Deletion start area {@link RectF} in screen coordinates. + * + * Getter for deletion area set with {@link Builder#setDeletionStartArea(RectF)}. + */ + @NonNull + public RectF getDeletionStartArea() { + return mStartArea; + } + + /** + * Returns the Deletion end area {@link RectF} in screen coordinates. + * + * Getter for deletion area set with {@link Builder#setDeletionEndArea(RectF)}. + */ + @NonNull + public RectF getDeletionEndArea() { + return mEndArea; + } + + /** + * Builder for {@link DeleteRangeGesture}. This class is not designed to be thread-safe. + */ + public static final class Builder { + private int mGranularity; + private RectF mStartArea; + private RectF mEndArea; + private String mFallbackText; + + /** + * Define text deletion granularity. Intersecting words/characters will be + * included in the operation. + * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or + * {@link HandwritingGesture#GRANULARITY_CHARACTER}. + * @return {@link Builder}. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setGranularity(@Granularity int granularity) { + mGranularity = granularity; + return this; + } + + /** + * Set rectangular single/multiline start of text deletion area intersecting with text. + * + * The resulting deletion is performed from the start of first word/character in the start + * rectangle to the end of the last word/character in the end rectangle + * {@link #setDeletionEndArea(RectF)}. + * <br/> + * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting + * /delete_range_gesture_rects.png" + * height="300" alt="Deletion strategy using two rectangles"/> + * <br/> + * + * Intersection is determined using + * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes + * all the words with their width/height center included in the deletion rectangle. + * @param startArea {@link RectF} (in screen coordinates) for start of deletion. This + * rectangle belongs to first line where deletion should start. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setDeletionStartArea(@NonNull RectF startArea) { + mStartArea = startArea; + return this; + } + + /** + * Set rectangular single/multiline end of text deletion area intersecting with text. + * + * The resulting deletion is performed from the start of first word/character in the start + * rectangle {@link #setDeletionStartArea(RectF)} to the end of the last word/character in + * the end rectangle. + * <br/> + * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting + * /delete_range_gesture_rects.png" + * height="300" alt="Deletion strategy using two rectangles"/> + * + * Intersection is determined using + * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes + * all the words with their width/height center included in the deletion rectangle. + * @param endArea {@link RectF} (in screen coordinates) for start of deletion. This + * rectangle belongs to the last line where deletion should end. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setDeletionEndArea(@NonNull RectF endArea) { + mEndArea = endArea; + return this; + } + + /** + * Set fallback text that will be committed at current cursor position if there is no + * applicable text beneath the area of gesture. + * @param fallbackText text to set + */ + @NonNull + public Builder setFallbackText(@Nullable String fallbackText) { + mFallbackText = fallbackText; + return this; + } + + /** + * @return {@link DeleteRangeGesture} using parameters in this + * {@link DeleteRangeGesture.Builder}. + * @throws IllegalArgumentException if one or more positional parameters are not specified. + */ + @NonNull + public DeleteRangeGesture build() { + if (mStartArea == null || mStartArea.isEmpty() || mEndArea == null + || mEndArea.isEmpty()) { + throw new IllegalArgumentException("Deletion area must be set."); + } + if (mGranularity <= GRANULARITY_UNDEFINED) { + throw new IllegalArgumentException("Deletion granularity must be set."); + } + return new DeleteRangeGesture(mGranularity, mStartArea, mEndArea, mFallbackText); + } + } + + /** + * Used to make this class parcelable. + */ + @NonNull + public static final Creator<DeleteRangeGesture> CREATOR = + new Creator<DeleteRangeGesture>() { + @Override + public DeleteRangeGesture createFromParcel(Parcel source) { + return new DeleteRangeGesture(source); + } + + @Override + public DeleteRangeGesture[] newArray(int size) { + return new DeleteRangeGesture[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mGranularity, mStartArea, mEndArea, mFallbackText); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DeleteRangeGesture)) return false; + + DeleteRangeGesture that = (DeleteRangeGesture) o; + + if (mGranularity != that.mGranularity) return false; + if (!Objects.equals(mFallbackText, that.mFallbackText)) return false; + if (!Objects.equals(mStartArea, that.mStartArea)) return false; + return Objects.equals(mEndArea, that.mEndArea); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mFallbackText); + dest.writeInt(mGranularity); + dest.writeTypedObject(mStartArea, flags); + dest.writeTypedObject(mEndArea, flags); + } +} diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index c3b32c9f1d54..4a79ba62de69 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -557,10 +557,14 @@ public class EditorInfo implements InputType, Parcelable { Objects.requireNonNull(gesture); if (gesture.equals(SelectGesture.class)) { supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT; + } else if (gesture.equals(SelectRangeGesture.class)) { + supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT_RANGE; } else if (gesture.equals(InsertGesture.class)) { supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT; } else if (gesture.equals(DeleteGesture.class)) { supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE; + } else if (gesture.equals(DeleteRangeGesture.class)) { + supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE_RANGE; } else if (gesture.equals(RemoveSpaceGesture.class)) { supportedTypes |= HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE; } else if (gesture.equals(JoinOrSplitGesture.class)) { diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java index 494aaaa32f57..07b1e1f0468e 100644 --- a/core/java/android/view/inputmethod/HandwritingGesture.java +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -25,6 +25,7 @@ import android.view.MotionEvent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; import java.util.concurrent.Executor; import java.util.function.IntConsumer; @@ -106,14 +107,26 @@ public abstract class HandwritingGesture { public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 1 << 4; /** + * Gesture of type {@link SelectRangeGesture} to select range of text. + */ + public static final int GESTURE_TYPE_SELECT_RANGE = 1 << 5; + + /** + * Gesture of type {@link DeleteRangeGesture} to delete range of text. + */ + public static final int GESTURE_TYPE_DELETE_RANGE = 1 << 6; + + /** * Type of gesture like {@link #GESTURE_TYPE_SELECT}, {@link #GESTURE_TYPE_INSERT}, * or {@link #GESTURE_TYPE_DELETE}. */ @IntDef(prefix = {"GESTURE_TYPE_"}, value = { GESTURE_TYPE_NONE, GESTURE_TYPE_SELECT, + GESTURE_TYPE_SELECT_RANGE, GESTURE_TYPE_INSERT, GESTURE_TYPE_DELETE, + GESTURE_TYPE_DELETE_RANGE, GESTURE_TYPE_REMOVE_SPACE, GESTURE_TYPE_JOIN_OR_SPLIT}) @Retention(RetentionPolicy.SOURCE) @@ -123,13 +136,15 @@ public abstract class HandwritingGesture { * Flags which can be any combination of {@link #GESTURE_TYPE_SELECT}, * {@link #GESTURE_TYPE_INSERT}, or {@link #GESTURE_TYPE_DELETE}. * {@link GestureTypeFlags} can be used by editors to declare what gestures are supported - * and report them in {@link EditorInfo#setSupportedHandwritingGestureTypes(int)}. + * and report them in {@link EditorInfo#setSupportedHandwritingGestures(List)}. * @hide */ @IntDef(flag = true, prefix = {"GESTURE_TYPE_"}, value = { GESTURE_TYPE_SELECT, + GESTURE_TYPE_SELECT_RANGE, GESTURE_TYPE_INSERT, GESTURE_TYPE_DELETE, + GESTURE_TYPE_DELETE_RANGE, GESTURE_TYPE_REMOVE_SPACE, GESTURE_TYPE_JOIN_OR_SPLIT}) @Retention(RetentionPolicy.SOURCE) @@ -140,7 +155,7 @@ public abstract class HandwritingGesture { /** * Returns the gesture type {@link GestureType}. * {@link GestureType} can be used by editors to declare what gestures are supported and report - * them in {@link EditorInfo#setSupportedHandwritingGestureTypes(int)}. + * them in {@link EditorInfo#setSupportedHandwritingGestures(List)}. * @hide */ @TestApi diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0c7c1639e6c7..c966e93a71c5 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -788,7 +788,7 @@ public final class InputMethodManager { // we'll just do a window focus gain and call it a day. View servedView = controller.getServedView(); boolean nextFocusHasConnection = servedView != null && servedView == focusedView - && hasActiveConnection(focusedView); + && hasActiveInputConnectionInternal(focusedView); if (DEBUG) { Log.v(TAG, "Reporting focus gain, without startInput" + ", nextFocusIsServedView=" + nextFocusHasConnection); @@ -861,22 +861,11 @@ public final class InputMethodManager { /** * Checks whether the active input connection (if any) is for the given view. * - * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic. - * - * Note that this method is only intended for restarting input after focus gain - * (e.g. b/160391516), DO NOT leverage this method to do another check. + * @see #hasActiveInputConnectionInternal(View)} */ @Override public boolean hasActiveConnection(View view) { - synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) { - return false; - } - - return mServedInputConnection != null - && mServedInputConnection.isActive() - && mServedInputConnection.getServedView() == view; - } + return hasActiveInputConnectionInternal(view); } } @@ -889,11 +878,31 @@ public final class InputMethodManager { * Checks whether the active input connection (if any) is for the given view. * * @hide - * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View) + * @see #hasActiveInputConnectionInternal(View)} */ @TestApi public boolean hasActiveInputConnection(@Nullable View view) { - return mDelegate.hasActiveConnection(view); + return hasActiveInputConnectionInternal(view); + } + + /** + * Checks whether the active input connection (if any) is for the given view. + * + * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic. + * + * Note that this method is only intended for restarting input after focus gain + * (e.g. b/160391516), DO NOT leverage this method to do another check. + */ + private boolean hasActiveInputConnectionInternal(@Nullable View view) { + synchronized (mH) { + if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) { + return false; + } + + return mServedInputConnection != null + && mServedInputConnection.isActive() + && mServedInputConnection.getServedView() == view; + } } @GuardedBy("mH") diff --git a/core/java/android/view/inputmethod/SelectGesture.java b/core/java/android/view/inputmethod/SelectGesture.java index 2f02e66d4295..6dc4ed295669 100644 --- a/core/java/android/view/inputmethod/SelectGesture.java +++ b/core/java/android/view/inputmethod/SelectGesture.java @@ -27,9 +27,11 @@ import android.widget.TextView; import java.util.Objects; /** - * A sub-class of {@link HandwritingGesture} for selecting an area of text. + * A sub-class of {@link HandwritingGesture} for selecting an area of text using single rectangle. * This class holds the information required for selection of text in * toolkit widgets like {@link TextView}. + * <p>Note: This selects all text <em>within</em> the given area. To select a range <em>between</em> + * two areas, use {@link SelectRangeGesture}.</p> */ public final class SelectGesture extends HandwritingGesture implements Parcelable { diff --git a/core/java/android/view/inputmethod/SelectRangeGesture.aidl b/core/java/android/view/inputmethod/SelectRangeGesture.aidl new file mode 100644 index 000000000000..a7bce0adddf0 --- /dev/null +++ b/core/java/android/view/inputmethod/SelectRangeGesture.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable SelectRangeGesture;
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/SelectRangeGesture.java b/core/java/android/view/inputmethod/SelectRangeGesture.java new file mode 100644 index 000000000000..7cb60023bd31 --- /dev/null +++ b/core/java/android/view/inputmethod/SelectRangeGesture.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.TextView; + +import java.util.Objects; + +/** + * A subclass of {@link HandwritingGesture} for selecting a range of text by defining start and end + * rectangles. This can be useful when the range cannot be defined with a single rectangle. + * This class holds the information required for selection of text in + * toolkit widgets like {@link TextView}. + * <p>Note: this selects text within a range <em>between</em> two given areas. To select all text + * <em>within</em> a single area, use {@link SelectGesture}</p> + */ +public final class SelectRangeGesture extends HandwritingGesture implements Parcelable { + + private @Granularity int mGranularity; + private RectF mStartArea; + private RectF mEndArea; + + private SelectRangeGesture( + int granularity, RectF startArea, RectF endArea, String fallbackText) { + mType = GESTURE_TYPE_SELECT_RANGE; + mStartArea = startArea; + mEndArea = endArea; + mGranularity = granularity; + mFallbackText = fallbackText; + } + + private SelectRangeGesture(@NonNull Parcel source) { + mType = GESTURE_TYPE_SELECT_RANGE; + mFallbackText = source.readString8(); + mGranularity = source.readInt(); + mStartArea = source.readTypedObject(RectF.CREATOR); + mEndArea = source.readTypedObject(RectF.CREATOR); + } + + /** + * Returns Granular level on which text should be operated. + * @see #GRANULARITY_CHARACTER + * @see #GRANULARITY_WORD + */ + @Granularity + public int getGranularity() { + return mGranularity; + } + + /** + * Returns the Selection start area {@link RectF} in screen coordinates. + * + * Getter for selection area set with {@link Builder#setSelectionStartArea(RectF)}. + */ + @NonNull + public RectF getSelectionStartArea() { + return mStartArea; + } + + /** + * Returns the Selection end area {@link RectF} in screen coordinates. + * + * Getter for selection area set with {@link Builder#setSelectionEndArea(RectF)}. + */ + @NonNull + public RectF getSelectionEndArea() { + return mEndArea; + } + + + /** + * Builder for {@link SelectRangeGesture}. This class is not designed to be thread-safe. + */ + public static final class Builder { + private int mGranularity; + private RectF mStartArea; + private RectF mEndArea; + private String mFallbackText; + + /** + * Define text selection granularity. Intersecting words/characters will be + * included in the operation. + * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or + * {@link HandwritingGesture#GRANULARITY_CHARACTER}. + * @return {@link Builder}. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setGranularity(@HandwritingGesture.Granularity int granularity) { + mGranularity = granularity; + return this; + } + + /** + * Set rectangular single/multiline start of text selection area intersecting with text. + * + * The resulting selection is performed from the start of first word/character in the start + * rectangle to the end of the last word/character in the end rectangle + * {@link #setSelectionEndArea(RectF)}. + * <br/> + * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting + * /select_range_gesture_rects.png" + * height="300" alt="Selection strategy using two rectangles"/> + * <br/> + * + * Intersection is determined using + * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes + * all the words with their width/height center included in the selection rectangle. + * @param startArea {@link RectF} (in screen coordinates) for start of selection. This + * rectangle belongs to first line where selection should start. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setSelectionStartArea(@NonNull RectF startArea) { + mStartArea = startArea; + return this; + } + + /** + * Set rectangular single/multiline end of text selection area intersecting with text. + * + * The resulting selection is performed from the start of first word/character in the start + * rectangle {@link #setSelectionStartArea(RectF)} to the end of the last word/character in + * the end rectangle. + * <br/> + * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting + * /select_range_gesture_rects.png" + * height="300" alt="Selection strategy using two rectangles"/> + * <br/> + * + * The selection includes the first word/character in the rectangle, the last + * word/character in the rectangle, and everything in between even if it's not in the + * rectangle. + * + * Intersection is determined using + * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes + * all the words with their width/height center included in the selection rectangle. + * @param endArea {@link RectF} (in screen coordinates) for start of selection. This + * rectangle belongs to the last line where selection should end. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + public Builder setSelectionEndArea(@NonNull RectF endArea) { + mEndArea = endArea; + return this; + } + + /** + * Set fallback text that will be committed at current cursor position if there is no + * applicable text beneath the area of gesture. + * @param fallbackText text to set + */ + @NonNull + public Builder setFallbackText(@Nullable String fallbackText) { + mFallbackText = fallbackText; + return this; + } + + /** + * @return {@link SelectRangeGesture} using parameters in this + * {@link SelectRangeGesture.Builder}. + * @throws IllegalArgumentException if one or more positional parameters are not specified. + */ + @NonNull + public SelectRangeGesture build() { + if (mStartArea == null || mStartArea.isEmpty() || mEndArea == null + || mEndArea.isEmpty()) { + throw new IllegalArgumentException("Selection area must be set."); + } + if (mGranularity <= GRANULARITY_UNDEFINED) { + throw new IllegalArgumentException("Selection granularity must be set."); + } + return new SelectRangeGesture(mGranularity, mStartArea, mEndArea, mFallbackText); + } + } + + /** + * Used to make this class parcelable. + */ + @NonNull + public static final Parcelable.Creator<SelectRangeGesture> CREATOR = + new Parcelable.Creator<SelectRangeGesture>() { + @Override + public SelectRangeGesture createFromParcel(Parcel source) { + return new SelectRangeGesture(source); + } + + @Override + public SelectRangeGesture[] newArray(int size) { + return new SelectRangeGesture[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mGranularity, mStartArea, mEndArea, mFallbackText); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SelectRangeGesture)) return false; + + SelectRangeGesture that = (SelectRangeGesture) o; + + if (mGranularity != that.mGranularity) return false; + if (!Objects.equals(mFallbackText, that.mFallbackText)) return false; + if (!Objects.equals(mStartArea, that.mStartArea)) return false; + return Objects.equals(mEndArea, that.mEndArea); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mFallbackText); + dest.writeInt(mGranularity); + dest.writeTypedObject(mStartArea, flags); + dest.writeTypedObject(mEndArea, flags); + } +} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index cebaa76e7406..424b8aef7dae 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2875,6 +2875,17 @@ public class Editor { } } + /** + * + * @return whether the Blink runnable is blinking or not, if null return false. + * @hide + */ + @VisibleForTesting + public boolean isBlinking() { + if (mBlink == null) return false; + return !mBlink.mCancelled; + } + private class Blink implements Runnable { private boolean mCancelled; @@ -4583,7 +4594,6 @@ public class Editor { */ private final class CursorAnchorInfoNotifier implements TextViewPositionListener { final CursorAnchorInfo.Builder mSelectionInfoBuilder = new CursorAnchorInfo.Builder(); - final int[] mTmpIntOffset = new int[2]; final Matrix mViewToScreenMatrix = new Matrix(); @Override @@ -4635,9 +4645,8 @@ public class Editor { builder.setSelectionRange(selectionStart, mTextView.getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. - mViewToScreenMatrix.set(mTextView.getMatrix()); - mTextView.getLocationOnScreen(mTmpIntOffset); - mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]); + mViewToScreenMatrix.reset(); + mTextView.transformMatrixToGlobal(mViewToScreenMatrix); builder.setMatrix(mViewToScreenMatrix); if (includeEditorBounds) { diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java new file mode 100644 index 000000000000..887d027d26a8 --- /dev/null +++ b/core/java/android/window/ScreenCapture.java @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.HardwareBuffer; +import android.os.IBinder; +import android.util.Log; +import android.view.SurfaceControl; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Handles display and layer captures for the system. + * + * @hide + */ +public class ScreenCapture { + private static final String TAG = "ScreenCapture"; + + private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, + ScreenCaptureListener captureListener); + private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, + ScreenCaptureListener captureListener); + + /** + * @param captureArgs Arguments about how to take the screenshot + * @param captureListener A listener to receive the screenshot callback + * @hide + */ + public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs, + @NonNull ScreenCaptureListener captureListener) { + return nativeCaptureDisplay(captureArgs, captureListener); + } + + /** + * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with + * the content. + * + * @hide + */ + public static ScreenshotHardwareBuffer captureDisplay( + DisplayCaptureArgs captureArgs) { + SyncScreenCaptureListener + screenCaptureListener = new SyncScreenCaptureListener(); + + int status = captureDisplay(captureArgs, screenCaptureListener); + if (status != 0) { + return null; + } + + return screenCaptureListener.waitForScreenshot(); + } + + /** + * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. + * + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. If the root layer does not + * have a buffer or a crop set, then a non-empty source crop must be + * specified. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + * + * @return Returns a HardwareBuffer that contains the layer capture. + * @hide + */ + public static ScreenshotHardwareBuffer captureLayers(SurfaceControl layer, Rect sourceCrop, + float frameScale) { + return captureLayers(layer, sourceCrop, frameScale, PixelFormat.RGBA_8888); + } + + /** + * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. + * + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. If the root layer does not + * have a buffer or a crop set, then a non-empty source crop must be + * specified. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + * @param format The desired pixel format of the returned buffer. + * + * @return Returns a HardwareBuffer that contains the layer capture. + * @hide + */ + public static ScreenshotHardwareBuffer captureLayers(@NonNull SurfaceControl layer, + @Nullable Rect sourceCrop, float frameScale, int format) { + LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer) + .setSourceCrop(sourceCrop) + .setFrameScale(frameScale) + .setPixelFormat(format) + .build(); + + return captureLayers(captureArgs); + } + + /** + * @hide + */ + public static ScreenshotHardwareBuffer captureLayers( + LayerCaptureArgs captureArgs) { + SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); + + int status = captureLayers(captureArgs, screenCaptureListener); + if (status != 0) { + return null; + } + + return screenCaptureListener.waitForScreenshot(); + } + + /** + * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer + * handles to exclude. + * @hide + */ + public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer, + Rect sourceCrop, float frameScale, int format, SurfaceControl[] exclude) { + LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer) + .setSourceCrop(sourceCrop) + .setFrameScale(frameScale) + .setPixelFormat(format) + .setExcludeLayers(exclude) + .build(); + + return captureLayers(captureArgs); + } + + /** + * @param captureArgs Arguments about how to take the screenshot + * @param captureListener A listener to receive the screenshot callback + * @hide + */ + public static int captureLayers(@NonNull LayerCaptureArgs captureArgs, + @NonNull ScreenCaptureListener captureListener) { + return nativeCaptureLayers(captureArgs, captureListener); + } + + /** + * @hide + */ + public interface ScreenCaptureListener { + /** + * The callback invoked when the screen capture is complete. + * @param hardwareBuffer Data containing info about the screen capture. + */ + void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer); + } + + /** + * A wrapper around HardwareBuffer that contains extra information about how to + * interpret the screenshot HardwareBuffer. + * + * @hide + */ + public static class ScreenshotHardwareBuffer { + private final HardwareBuffer mHardwareBuffer; + private final ColorSpace mColorSpace; + private final boolean mContainsSecureLayers; + private final boolean mContainsHdrLayers; + + public ScreenshotHardwareBuffer(HardwareBuffer hardwareBuffer, ColorSpace colorSpace, + boolean containsSecureLayers, boolean containsHdrLayers) { + mHardwareBuffer = hardwareBuffer; + mColorSpace = colorSpace; + mContainsSecureLayers = containsSecureLayers; + mContainsHdrLayers = containsHdrLayers; + } + + /** + * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. + * @param hardwareBuffer The existing HardwareBuffer object + * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} + * @param containsSecureLayers Indicates whether this graphic buffer contains captured + * contents of secure layers, in which case the screenshot + * should not be persisted. + * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. + */ + private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer, + int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) { + ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]); + return new ScreenshotHardwareBuffer( + hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers); + } + + public ColorSpace getColorSpace() { + return mColorSpace; + } + + public HardwareBuffer getHardwareBuffer() { + return mHardwareBuffer; + } + + /** + * Whether this screenshot contains secure layers + */ + public boolean containsSecureLayers() { + return mContainsSecureLayers; + } + /** + * Returns whether the screenshot contains at least one HDR layer. + * This information may be useful for informing the display whether this screenshot + * is allowed to be dimmed to SDR white. + */ + public boolean containsHdrLayers() { + return mContainsHdrLayers; + } + + /** + * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it. + * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap + * into + * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)} + * + * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to + * directly + * use the {@link HardwareBuffer} directly. + * + * @return Bitmap generated from the {@link HardwareBuffer} + */ + public Bitmap asBitmap() { + if (mHardwareBuffer == null) { + Log.w(TAG, "Failed to take screenshot. Null screenshot object"); + return null; + } + return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace); + } + } + + private static class SyncScreenCaptureListener implements ScreenCaptureListener { + private static final int SCREENSHOT_WAIT_TIME_S = 1; + private ScreenshotHardwareBuffer mScreenshotHardwareBuffer; + private final CountDownLatch mCountDownLatch = new CountDownLatch(1); + + @Override + public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) { + mScreenshotHardwareBuffer = hardwareBuffer; + mCountDownLatch.countDown(); + } + + private ScreenshotHardwareBuffer waitForScreenshot() { + try { + mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait for screen capture result", e); + } + + return mScreenshotHardwareBuffer; + } + } + + /** + * A common arguments class used for various screenshot requests. This contains arguments that + * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs} + * @hide + */ + private abstract static class CaptureArgs { + private final int mPixelFormat; + private final Rect mSourceCrop = new Rect(); + private final float mFrameScaleX; + private final float mFrameScaleY; + private final boolean mCaptureSecureLayers; + private final boolean mAllowProtected; + private final long mUid; + private final boolean mGrayscale; + + private CaptureArgs(Builder<? extends Builder<?>> builder) { + mPixelFormat = builder.mPixelFormat; + mSourceCrop.set(builder.mSourceCrop); + mFrameScaleX = builder.mFrameScaleX; + mFrameScaleY = builder.mFrameScaleY; + mCaptureSecureLayers = builder.mCaptureSecureLayers; + mAllowProtected = builder.mAllowProtected; + mUid = builder.mUid; + mGrayscale = builder.mGrayscale; + } + + /** + * The Builder class used to construct {@link CaptureArgs} + * + * @param <T> A builder that extends {@link Builder} + */ + abstract static class Builder<T extends Builder<T>> { + private int mPixelFormat = PixelFormat.RGBA_8888; + private final Rect mSourceCrop = new Rect(); + private float mFrameScaleX = 1; + private float mFrameScaleY = 1; + private boolean mCaptureSecureLayers; + private boolean mAllowProtected; + private long mUid = -1; + private boolean mGrayscale; + + /** + * The desired pixel format of the returned buffer. + */ + public T setPixelFormat(int pixelFormat) { + mPixelFormat = pixelFormat; + return getThis(); + } + + /** + * The portion of the screen to capture into the buffer. Caller may pass in + * 'new Rect()' or null if no cropping is desired. + */ + public T setSourceCrop(@Nullable Rect sourceCrop) { + if (sourceCrop == null) { + mSourceCrop.setEmpty(); + } else { + mSourceCrop.set(sourceCrop); + } + return getThis(); + } + + /** + * The desired scale of the returned buffer. The raw screen will be scaled up/down. + */ + public T setFrameScale(float frameScale) { + mFrameScaleX = frameScale; + mFrameScaleY = frameScale; + return getThis(); + } + + /** + * The desired scale of the returned buffer, allowing separate values for x and y scale. + * The raw screen will be scaled up/down. + */ + public T setFrameScale(float frameScaleX, float frameScaleY) { + mFrameScaleX = frameScaleX; + mFrameScaleY = frameScaleY; + return getThis(); + } + + /** + * Whether to allow the screenshot of secure layers. Warning: This should only be done + * if the content will be placed in a secure SurfaceControl. + * + * @see ScreenshotHardwareBuffer#containsSecureLayers() + */ + public T setCaptureSecureLayers(boolean captureSecureLayers) { + mCaptureSecureLayers = captureSecureLayers; + return getThis(); + } + + /** + * Whether to allow the screenshot of protected (DRM) content. Warning: The screenshot + * cannot be read in unprotected space. + * + * @see HardwareBuffer#USAGE_PROTECTED_CONTENT + */ + public T setAllowProtected(boolean allowProtected) { + mAllowProtected = allowProtected; + return getThis(); + } + + /** + * Set the uid of the content that should be screenshot. The code will skip any surfaces + * that don't belong to the specified uid. + */ + public T setUid(long uid) { + mUid = uid; + return getThis(); + } + + /** + * Set whether the screenshot should use grayscale or not. + */ + public T setGrayscale(boolean grayscale) { + mGrayscale = grayscale; + return getThis(); + } + + /** + * Each sub class should return itself to allow the builder to chain properly + */ + abstract T getThis(); + } + } + + /** + * The arguments class used to make display capture requests. + * + * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener) + * @hide + */ + public static class DisplayCaptureArgs extends CaptureArgs { + private final IBinder mDisplayToken; + private final int mWidth; + private final int mHeight; + private final boolean mUseIdentityTransform; + + private DisplayCaptureArgs(Builder builder) { + super(builder); + mDisplayToken = builder.mDisplayToken; + mWidth = builder.mWidth; + mHeight = builder.mHeight; + mUseIdentityTransform = builder.mUseIdentityTransform; + } + + /** + * The Builder class used to construct {@link DisplayCaptureArgs} + */ + public static class Builder extends CaptureArgs.Builder<Builder> { + private IBinder mDisplayToken; + private int mWidth; + private int mHeight; + private boolean mUseIdentityTransform; + + /** + * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder + * remains valid. + */ + public DisplayCaptureArgs build() { + if (mDisplayToken == null) { + throw new IllegalStateException( + "Can't take screenshot with null display token"); + } + return new DisplayCaptureArgs(this); + } + + public Builder(IBinder displayToken) { + setDisplayToken(displayToken); + } + + /** + * The display to take the screenshot of. + */ + public Builder setDisplayToken(IBinder displayToken) { + mDisplayToken = displayToken; + return this; + } + + /** + * Set the desired size of the returned buffer. The raw screen will be scaled down to + * this size + * + * @param width The desired width of the returned buffer. Caller may pass in 0 if no + * scaling is desired. + * @param height The desired height of the returned buffer. Caller may pass in 0 if no + * scaling is desired. + */ + public Builder setSize(int width, int height) { + mWidth = width; + mHeight = height; + return this; + } + + /** + * Replace the rotation transform of the display with the identity transformation while + * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0 + * orientation. Set this value to false if the screenshot should be taken in the + * current screen orientation. + */ + public Builder setUseIdentityTransform(boolean useIdentityTransform) { + mUseIdentityTransform = useIdentityTransform; + return this; + } + + @Override + Builder getThis() { + return this; + } + } + } + + /** + * The arguments class used to make layer capture requests. + * + * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener) + * @hide + */ + public static class LayerCaptureArgs extends CaptureArgs { + private final long mNativeLayer; + private final long[] mNativeExcludeLayers; + private final boolean mChildrenOnly; + + private LayerCaptureArgs(Builder builder) { + super(builder); + mChildrenOnly = builder.mChildrenOnly; + mNativeLayer = builder.mLayer.mNativeObject; + if (builder.mExcludeLayers != null) { + mNativeExcludeLayers = new long[builder.mExcludeLayers.length]; + for (int i = 0; i < builder.mExcludeLayers.length; i++) { + mNativeExcludeLayers[i] = builder.mExcludeLayers[i].mNativeObject; + } + } else { + mNativeExcludeLayers = null; + } + } + + /** + * The Builder class used to construct {@link LayerCaptureArgs} + */ + public static class Builder extends CaptureArgs.Builder<Builder> { + private SurfaceControl mLayer; + private SurfaceControl[] mExcludeLayers; + private boolean mChildrenOnly = true; + + /** + * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder + * remains valid. + */ + public LayerCaptureArgs build() { + if (mLayer == null) { + throw new IllegalStateException( + "Can't take screenshot with null layer"); + } + return new LayerCaptureArgs(this); + } + + public Builder(SurfaceControl layer) { + setLayer(layer); + } + + /** + * The root layer to capture. + */ + public Builder setLayer(SurfaceControl layer) { + mLayer = layer; + return this; + } + + + /** + * An array of layer handles to exclude. + */ + public Builder setExcludeLayers(@Nullable SurfaceControl[] excludeLayers) { + mExcludeLayers = excludeLayers; + return this; + } + + /** + * Whether to include the layer itself in the screenshot or just the children and their + * descendants. + */ + public Builder setChildrenOnly(boolean childrenOnly) { + mChildrenOnly = childrenOnly; + return this; + } + + @Override + Builder getThis() { + return this; + } + + } + } + +} diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 8ca763e81757..33ea2e4af473 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -110,11 +110,11 @@ public final class TransitionInfo implements Parcelable { /** The container is an input-method window. */ public static final int FLAG_IS_INPUT_METHOD = 1 << 8; - /** The container is ActivityEmbedding embedded. */ - public static final int FLAG_IS_EMBEDDED = 1 << 9; + /** The container is in a Task with embedded activity. */ + public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9; - /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 10; + /** The container fills its parent Task before and after the transition. */ + public static final int FLAG_FILLS_TASK = 1 << 10; /** The container is going to show IME on its task after the transition. */ public static final int FLAG_WILL_IME_SHOWN = 1 << 11; @@ -125,6 +125,9 @@ public final class TransitionInfo implements Parcelable { /** The container attaches work profile thumbnail for cross profile animation. */ public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ + public static final int FLAG_FIRST_CUSTOM = 1 << 14; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -137,9 +140,12 @@ public final class TransitionInfo implements Parcelable { FLAG_OCCLUDES_KEYGUARD, FLAG_DISPLAY_HAS_ALERT_WINDOWS, FLAG_IS_INPUT_METHOD, - FLAG_IS_EMBEDDED, - FLAG_FIRST_CUSTOM, - FLAG_WILL_IME_SHOWN + FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, + FLAG_FILLS_TASK, + FLAG_WILL_IME_SHOWN, + FLAG_CROSS_PROFILE_OWNER_THUMBNAIL, + FLAG_CROSS_PROFILE_WORK_THUMBNAIL, + FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -322,28 +328,31 @@ public final class TransitionInfo implements Parcelable { sb.append("IS_INPUT_METHOD"); } if ((flags & FLAG_TRANSLUCENT) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "TRANSLUCENT"); + sb.append(sb.length() == 0 ? "" : "|").append("TRANSLUCENT"); } if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "STARTING_WINDOW_TRANSFER"); + sb.append(sb.length() == 0 ? "" : "|").append("STARTING_WINDOW_TRANSFER"); } if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION"); + sb.append(sb.length() == 0 ? "" : "|").append("IS_VOICE_INTERACTION"); } if ((flags & FLAG_IS_DISPLAY) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY"); + sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY"); } if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD"); + sb.append(sb.length() == 0 ? "" : "|").append("OCCLUDES_KEYGUARD"); } if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS"); + sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS"); + } + if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY"); } - if ((flags & FLAG_IS_EMBEDDED) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED"); + if ((flags & FLAG_FILLS_TASK) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK"); } if ((flags & FLAG_FIRST_CUSTOM) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM"); + sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } return sb.toString(); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 83bf801391f0..8d51c9cdca9c 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -270,7 +270,6 @@ interface IVoiceInteractionManagerService { */ void shutdownHotwordDetectionService(); - @EnforcePermission(allOf={"RECORD_AUDIO", "CAPTURE_AUDIO_HOTWORD"}) void startListeningFromMic( in AudioFormat audioFormat, in IMicrophoneHotwordDetectionVoiceInteractionCallback callback); @@ -286,7 +285,6 @@ interface IVoiceInteractionManagerService { /** * Test API to simulate to trigger hardware recognition event for test. */ - @EnforcePermission(allOf={"RECORD_AUDIO", "CAPTURE_AUDIO_HOTWORD"}) void triggerHardwareRecognitionEventForTest( in SoundTrigger.KeyphraseRecognitionEvent event, in IHotwordRecognitionStatusCallback callback); diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl index f456e85adff4..17f9b7dea045 100644 --- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl +++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl @@ -22,12 +22,14 @@ import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InsertGesture; import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture; import android.view.inputmethod.TextAttribute; import com.android.internal.infra.AndroidFuture; @@ -95,12 +97,18 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; void performHandwritingSelectGesture(in InputConnectionCommandHeader header, in SelectGesture gesture, in ResultReceiver resultReceiver); + void performHandwritingSelectRangeGesture(in InputConnectionCommandHeader header, + in SelectRangeGesture gesture, in ResultReceiver resultReceiver); + void performHandwritingInsertGesture(in InputConnectionCommandHeader header, in InsertGesture gesture, in ResultReceiver resultReceiver); void performHandwritingDeleteGesture(in InputConnectionCommandHeader header, in DeleteGesture gesture, in ResultReceiver resultReceiver); + void performHandwritingDeleteRangeGesture(in InputConnectionCommandHeader header, + in DeleteRangeGesture gesture, in ResultReceiver resultReceiver); + void performHandwritingRemoveSpaceGesture(in InputConnectionCommandHeader header, in RemoveSpaceGesture gesture, in ResultReceiver resultReceiver); diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index bbf0a764d9e4..7f3144bb5894 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -272,19 +272,24 @@ public final class InputMethodDebug { if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_SELECT) != 0) { joiner.add("SELECT"); } + if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) != 0) { + joiner.add("SELECT_RANGE"); + } if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_INSERT) != 0) { joiner.add("INSERT"); } if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_DELETE) != 0) { joiner.add("DELETE"); } + if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) != 0) { + joiner.add("DELETE_RANGE"); + } if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE) != 0) { joiner.add("REMOVE_SPACE"); } if ((gestureTypeFlags & HandwritingGesture.GESTURE_TYPE_JOIN_OR_SPLIT) != 0) { joiner.add("JOIN_OR_SPLIT"); } - return joiner.setEmptyValue("(none)").toString(); } diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 6a7028d31edb..713e913abce1 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -41,6 +41,7 @@ import android.view.ViewRootImpl; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.DumpableInputConnection; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; @@ -51,6 +52,7 @@ import android.view.inputmethod.InsertGesture; import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture; import android.view.inputmethod.TextAttribute; import android.view.inputmethod.TextSnapshot; @@ -985,6 +987,14 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub @Dispatching(cancellable = true) @Override + public void performHandwritingSelectRangeGesture( + InputConnectionCommandHeader header, SelectRangeGesture gesture, + ResultReceiver resultReceiver) { + performHandwritingGestureInternal(header, gesture, resultReceiver); + } + + @Dispatching(cancellable = true) + @Override public void performHandwritingInsertGesture( InputConnectionCommandHeader header, InsertGesture gesture, ResultReceiver resultReceiver) { @@ -1001,6 +1011,14 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub @Dispatching(cancellable = true) @Override + public void performHandwritingDeleteRangeGesture( + InputConnectionCommandHeader header, DeleteRangeGesture gesture, + ResultReceiver resultReceiver) { + performHandwritingGestureInternal(header, gesture, resultReceiver); + } + + @Dispatching(cancellable = true) + @Override public void performHandwritingRemoveSpaceGesture( InputConnectionCommandHeader header, RemoveSpaceGesture gesture, ResultReceiver resultReceiver) { diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index fd5846847c95..72a1bac28c9f 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -96,6 +96,9 @@ public class DisplayResolutionTracker { private void updateDisplay(int displayId) { DisplayInfo info = mManager.getDisplayInfo(displayId); + if (info == null) { + return; + } @Resolution int resolution = getResolution(info); synchronized (mLock) { diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index f097bf73eb76..552334486356 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -247,6 +247,13 @@ public class BatteryStatsHistory { SystemProperties.set("debug.tracing." + name, Integer.toString(value)); } } + + /** + * Records an instant event (one with no duration). + */ + public void traceInstantEvent(@NonNull String track, @NonNull String name) { + Trace.instantForTrack(Trace.TRACE_TAG_POWER, track, name); + } } private TraceDelegate mTracer; @@ -1165,6 +1172,25 @@ public class BatteryStatsHistory { } /** + * Writes event details into Atrace. + */ + private void recordTraceEvents(int code, HistoryTag tag) { + if (code == HistoryItem.EVENT_NONE) return; + if (!mTracer.tracingEnabled()) return; + + final int idx = code & HistoryItem.EVENT_TYPE_MASK; + final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" : + (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : ""; + + final String[] names = BatteryStats.HISTORY_EVENT_NAMES; + if (idx < 0 || idx >= names.length) return; + + final String track = "battery_stats." + names[idx]; + final String name = prefix + names[idx] + "=" + tag.uid + ":\"" + tag.string + "\""; + mTracer.traceInstantEvent(track, name); + } + + /** * Writes changes to a HistoryItem state bitmap to Atrace. */ private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) { @@ -1229,6 +1255,7 @@ public class BatteryStatsHistory { + Integer.toHexString(lastDiffStates2)); } + recordTraceEvents(cur.eventCode, cur.eventTag); recordTraceCounters(mHistoryLastWritten.states, cur.states & mActiveHistoryStates, BatteryStats.HISTORY_STATE_DESCRIPTIONS); recordTraceCounters(mHistoryLastWritten.states2, diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 50dcca64a146..664aeee6e299 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -98,6 +98,18 @@ public final class LongArrayMultiStateCounter implements Parcelable { native_getValues(mNativeObject, array); } + /** + * Combines contained values into a smaller array by aggregating them + * according to an index map. + */ + public boolean combineValues(long[] array, int[] indexMap) { + if (indexMap.length != mLength) { + throw new IllegalArgumentException( + "Wrong index map size " + indexMap.length + ", expected " + mLength); + } + return native_combineValues(mNativeObject, array, indexMap); + } + @Override public String toString() { final long[] array = new long[mLength]; @@ -116,6 +128,10 @@ public final class LongArrayMultiStateCounter implements Parcelable { @FastNative private native void native_getValues(long nativeObject, long[] array); + + @FastNative + private native boolean native_combineValues(long nativeObject, long[] array, + int[] indexMap); } private static final NativeAllocationRegistry sRegistry = diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index ea5f0b2f2144..b1e7d15cbf4a 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -1001,24 +1001,16 @@ public final class Zygote { } /** - * This will enable jdwp by default for all apps. It is OK to cache this property - * because we expect to reboot the system whenever this property changes - */ - private static final boolean ENABLE_JDWP = SystemProperties.get( - "persist.debuggable.dalvik.vm.jdwp.enabled").equals("1"); - - /** * Applies debugger system properties to the zygote arguments. * - * For eng builds all apps are debuggable. On userdebug and user builds - * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are - * debuggable. Otherwise, the debugger state is specified via the - * "--enable-jdwp" flag in the spawn request. + * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, + * the debugger state is specified via the "--enable-jdwp" flag + * in the spawn request. * * @param args non-null; zygote spawner args */ static void applyDebuggerSystemProperty(ZygoteArguments args) { - if (Build.IS_ENG || ENABLE_JDWP) { + if (RoSystemProperties.DEBUGGABLE) { args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; } } diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index 80d50ff5d310..c08f26492289 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -18,6 +18,7 @@ package com.android.internal.widget; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Paint; @@ -32,6 +33,7 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.view.ISystemGestureExclusionListener; import android.view.InputDevice; import android.view.KeyEvent; @@ -45,8 +47,6 @@ import android.view.WindowInsets; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicyConstants.PointerEventListener; -import java.util.ArrayList; - public class PointerLocationView extends View implements InputDeviceListener, PointerEventListener { private static final String TAG = "Pointer"; @@ -61,6 +61,9 @@ public class PointerLocationView extends View implements InputDeviceListener, */ private static final String GESTURE_EXCLUSION_PROP = "debug.pointerlocation.showexclusion"; + // In case when it's in first time or no active pointer found, draw the empty state. + private static final PointerState EMPTY_POINTER_STATE = new PointerState(); + public static class PointerState { // Trace of previous points. private float[] mTraceX = new float[32]; @@ -149,7 +152,7 @@ public class PointerLocationView extends View implements InputDeviceListener, private int mMaxNumPointers; private int mActivePointerId; @UnsupportedAppUsage - private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>(); + private final SparseArray<PointerState> mPointers = new SparseArray<PointerState>(); private final PointerCoords mTempCoords = new PointerCoords(); private final Region mSystemGestureExclusion = new Region(); @@ -175,8 +178,6 @@ public class PointerLocationView extends View implements InputDeviceListener, mVC = ViewConfiguration.get(c); mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); - mTextPaint.setTextSize(10 - * getResources().getDisplayMetrics().density); mTextPaint.setARGB(255, 0, 0, 0); mTextBackgroundPaint = new Paint(); mTextBackgroundPaint.setAntiAlias(false); @@ -188,20 +189,19 @@ public class PointerLocationView extends View implements InputDeviceListener, mPaint.setAntiAlias(true); mPaint.setARGB(255, 255, 255, 255); mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(2); mCurrentPointPaint = new Paint(); mCurrentPointPaint.setAntiAlias(true); mCurrentPointPaint.setARGB(255, 255, 0, 0); mCurrentPointPaint.setStyle(Paint.Style.STROKE); - mCurrentPointPaint.setStrokeWidth(2); mTargetPaint = new Paint(); mTargetPaint.setAntiAlias(false); mTargetPaint.setARGB(255, 0, 0, 192); mPathPaint = new Paint(); mPathPaint.setAntiAlias(false); mPathPaint.setARGB(255, 0, 96, 255); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(1); + mPathPaint.setStyle(Paint.Style.STROKE); + + configureDensityDependentFactors(); mSystemGestureExclusionPaint = new Paint(); mSystemGestureExclusionPaint.setARGB(25, 255, 0, 0); @@ -211,8 +211,6 @@ public class PointerLocationView extends View implements InputDeviceListener, mSystemGestureExclusionRejectedPaint.setARGB(25, 0, 0, 255); mSystemGestureExclusionRejectedPaint.setStyle(Paint.Style.FILL_AND_STROKE); - PointerState ps = new PointerState(); - mPointers.add(ps); mActivePointerId = 0; mVelocity = VelocityTracker.obtain(); @@ -308,7 +306,7 @@ public class PointerLocationView extends View implements InputDeviceListener, // Pointer trace. for (int p = 0; p < NP; p++) { - final PointerState ps = mPointers.get(p); + final PointerState ps = mPointers.valueAt(p); // Draw path. final int N = ps.mTraceCount; @@ -319,7 +317,7 @@ public class PointerLocationView extends View implements InputDeviceListener, for (int i=0; i < N; i++) { float x = ps.mTraceX[i]; float y = ps.mTraceY[i]; - if (Float.isNaN(x)) { + if (Float.isNaN(x) || Float.isNaN(y)) { haveLast = false; continue; } @@ -418,10 +416,6 @@ public class PointerLocationView extends View implements InputDeviceListener, } private void drawLabels(Canvas canvas) { - if (mActivePointerId < 0 || mActivePointerId >= mPointers.size()) { - return; - } - final int w = getWidth() - mWaterfallInsets.left - mWaterfallInsets.right; final int itemW = w / 7; final int base = mHeaderPaddingTop - mTextMetrics.ascent + 1; @@ -429,7 +423,7 @@ public class PointerLocationView extends View implements InputDeviceListener, canvas.save(); canvas.translate(mWaterfallInsets.left, 0); - final PointerState ps = mPointers.get(mActivePointerId); + final PointerState ps = mPointers.get(mActivePointerId, EMPTY_POINTER_STATE); canvas.drawRect(0, mHeaderPaddingTop, itemW - 1, bottom, mTextBackgroundPaint); canvas.drawText(mText.clear() @@ -600,18 +594,13 @@ public class PointerLocationView extends View implements InputDeviceListener, @Override public void onPointerEvent(MotionEvent event) { final int action = event.getAction(); - int NP = mPointers.size(); if (action == MotionEvent.ACTION_DOWN || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down if (action == MotionEvent.ACTION_DOWN) { - for (int p=0; p<NP; p++) { - final PointerState ps = mPointers.get(p); - ps.clearTrace(); - ps.mCurDown = false; - } + mPointers.clear(); mCurDown = true; mCurNumPointers = 0; mMaxNumPointers = 0; @@ -627,18 +616,17 @@ public class PointerLocationView extends View implements InputDeviceListener, } final int id = event.getPointerId(index); - while (NP <= id) { - PointerState ps = new PointerState(); - mPointers.add(ps); - NP++; + PointerState ps = mPointers.get(id); + if (ps == null) { + ps = new PointerState(); + mPointers.put(id, ps); } - if (mActivePointerId < 0 || mActivePointerId >= NP + if (!mPointers.contains(mActivePointerId) || !mPointers.get(mActivePointerId).mCurDown) { mActivePointerId = id; } - final PointerState ps = mPointers.get(id); ps.mCurDown = true; InputDevice device = InputDevice.getDevice(event.getDeviceId()); ps.mHasBoundingBox = device != null && @@ -707,13 +695,13 @@ public class PointerLocationView extends View implements InputDeviceListener, >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP final int id = event.getPointerId(index); - if (id >= NP) { - Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize=" - + NP + " pointerindex=" + index + final PointerState ps = mPointers.get(id); + if (ps == null) { + Slog.wtf(TAG, "Could not find pointer id=" + id + " in mPointers map," + + " size=" + mPointers.size() + " pointerindex=" + index + " action=0x" + Integer.toHexString(action)); return; } - final PointerState ps = mPointers.get(id); ps.mCurDown = false; if (action == MotionEvent.ACTION_UP @@ -1016,4 +1004,19 @@ public class PointerLocationView extends View implements InputDeviceListener, } } }; + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + configureDensityDependentFactors(); + } + + // Compute size by display density. + private void configureDensityDependentFactors() { + final float density = getResources().getDisplayMetrics().density; + mTextPaint.setTextSize(10 * density); + mPaint.setStrokeWidth(1 * density); + mCurrentPointPaint.setStrokeWidth(1 * density); + mPathPaint.setStrokeWidth(1 * density); + } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 5498769fcf8f..26d0be139b64 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -231,6 +231,8 @@ cc_library_shared { "android_hardware_input_InputWindowHandle.cpp", "android_hardware_input_InputApplicationHandle.cpp", "android_window_WindowInfosListener.cpp", + "android_window_ScreenCapture.cpp", + "jni_common.cpp", ], static_libs: [ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 817b3154889b..11cb64cdf81a 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -211,6 +211,8 @@ extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_android_window_WindowInfosListener(JNIEnv* env); +extern int register_android_window_ScreenCapture(JNIEnv* env); +extern int register_jni_common(JNIEnv* env); // Namespace for Android Runtime flags applied during boot time. static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot"; @@ -1647,6 +1649,8 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader), REG_JNI(register_android_window_WindowInfosListener), + REG_JNI(register_android_window_ScreenCapture), + REG_JNI(register_jni_common), }; /* diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 14699e7097f4..a068008f5e22 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -22,7 +22,7 @@ per-file android_view_PointerIcon.* = file:/services/core/java/com/android/serve # WindowManager per-file android_graphics_BLASTBufferQueue.cpp = file:/services/core/java/com/android/server/wm/OWNERS per-file android_view_Surface* = file:/services/core/java/com/android/server/wm/OWNERS -per-file android_window_WindowInfosListener.cpp = file:/services/core/java/com/android/server/wm/OWNERS +per-file android_window_* = file:/services/core/java/com/android/server/wm/OWNERS # Resources per-file android_content_res_* = file:/core/java/android/content/res/OWNERS diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index f24c66695052..746f88cd823d 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2179,7 +2179,7 @@ static jint convertAudioMixToNative(JNIEnv *env, break; } - nAudioMix->mCriteria.add(nCriterion); + nAudioMix->mCriteria.push_back(nCriterion); env->DeleteLocalRef(jCriterion); } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index e64b2615e1e2..aefec6cbc2cb 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -21,7 +21,6 @@ #include <android-base/chrono_utils.h> #include <android/graphics/properties.h> #include <android/graphics/region.h> -#include <android/gui/BnScreenCaptureListener.h> #include <android/gui/BnWindowInfosReportedListener.h> #include <android/hardware/display/IDeviceProductInfoConstants.h> #include <android/os/IInputConstants.h> @@ -60,12 +59,12 @@ #include "android_os_Parcel.h" #include "android_util_Binder.h" #include "core_jni_helpers.h" +#include "jni_common.h" // ---------------------------------------------------------------------------- namespace android { -using gui::CaptureArgs; using gui::FocusRequest; static void doThrowNPE(JNIEnv* env) { @@ -130,37 +129,6 @@ static struct { jfieldID group; } gDisplayModeClassInfo; -static struct { - jfieldID bottom; - jfieldID left; - jfieldID right; - jfieldID top; -} gRectClassInfo; - -static struct { - jfieldID pixelFormat; - jfieldID sourceCrop; - jfieldID frameScaleX; - jfieldID frameScaleY; - jfieldID captureSecureLayers; - jfieldID allowProtected; - jfieldID uid; - jfieldID grayscale; -} gCaptureArgsClassInfo; - -static struct { - jfieldID displayToken; - jfieldID width; - jfieldID height; - jfieldID useIdentityTransform; -} gDisplayCaptureArgsClassInfo; - -static struct { - jfieldID layer; - jfieldID excludeLayers; - jfieldID childrenOnly; -} gLayerCaptureArgsClassInfo; - // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref. void DeleteScreenshot(void* addr, void* context) { delete ((ScreenshotClient*) context); @@ -220,16 +188,6 @@ static struct { static struct { jclass clazz; - jmethodID builder; -} gScreenshotHardwareBufferClassInfo; - -static struct { - jclass clazz; - jmethodID onScreenCaptureComplete; -} gScreenCaptureListenerClassInfo; - -static struct { - jclass clazz; jmethodID ctor; jfieldID defaultMode; jfieldID allowGroupSwitching; @@ -266,24 +224,6 @@ static struct { jmethodID invokeReleaseCallback; } gInvokeReleaseCallback; -class JNamedColorSpace { -public: - // ColorSpace.Named.SRGB.ordinal() = 0; - static constexpr jint SRGB = 0; - - // ColorSpace.Named.DISPLAY_P3.ordinal() = 7; - static constexpr jint DISPLAY_P3 = 7; -}; - -constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace) { - switch (dataspace) { - case ui::Dataspace::DISPLAY_P3: - return JNamedColorSpace::DISPLAY_P3; - default: - return JNamedColorSpace::SRGB; - } -} - constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) { switch (colorMode) { case ui::ColorMode::DISPLAY_P3: @@ -296,59 +236,6 @@ constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode } } -class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener { -public: - explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) { - env->GetJavaVM(&mVm); - screenCaptureListenerObject = env->NewGlobalRef(jobject); - LOG_ALWAYS_FATAL_IF(!screenCaptureListenerObject, "Failed to make global ref"); - } - - ~ScreenCaptureListenerWrapper() { - if (screenCaptureListenerObject) { - getenv()->DeleteGlobalRef(screenCaptureListenerObject); - screenCaptureListenerObject = nullptr; - } - } - - binder::Status onScreenCaptureCompleted( - const gui::ScreenCaptureResults& captureResults) override { - JNIEnv* env = getenv(); - if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) { - env->CallVoidMethod(screenCaptureListenerObject, - gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr); - return binder::Status::ok(); - } - captureResults.fenceResult.value()->waitForever(""); - jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( - env, captureResults.buffer->toAHardwareBuffer()); - const jint namedColorSpace = - fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace); - jobject screenshotHardwareBuffer = - env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, - gScreenshotHardwareBufferClassInfo.builder, - jhardwareBuffer, namedColorSpace, - captureResults.capturedSecureLayers, - captureResults.capturedHdrLayers); - env->CallVoidMethod(screenCaptureListenerObject, - gScreenCaptureListenerClassInfo.onScreenCaptureComplete, - screenshotHardwareBuffer); - env->DeleteLocalRef(jhardwareBuffer); - env->DeleteLocalRef(screenshotHardwareBuffer); - return binder::Status::ok(); - } - -private: - jobject screenCaptureListenerObject; - JavaVM* mVm; - - JNIEnv* getenv() { - JNIEnv* env; - mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); - return env; - } -}; - class TransactionCommittedListenerWrapper { public: explicit TransactionCommittedListenerWrapper(JNIEnv* env, jobject object) { @@ -494,102 +381,6 @@ static void nativeSetDefaultBufferSize(JNIEnv* env, jclass clazz, jlong nativeOb } } -static Rect rectFromObj(JNIEnv* env, jobject rectObj) { - int left = env->GetIntField(rectObj, gRectClassInfo.left); - int top = env->GetIntField(rectObj, gRectClassInfo.top); - int right = env->GetIntField(rectObj, gRectClassInfo.right); - int bottom = env->GetIntField(rectObj, gRectClassInfo.bottom); - return Rect(left, top, right, bottom); -} - -static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) { - captureArgs.pixelFormat = static_cast<ui::PixelFormat>( - env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat)); - captureArgs.sourceCrop = - rectFromObj(env, - env->GetObjectField(captureArgsObject, gCaptureArgsClassInfo.sourceCrop)); - captureArgs.frameScaleX = - env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX); - captureArgs.frameScaleY = - env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY); - captureArgs.captureSecureLayers = - env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers); - captureArgs.allowProtected = - env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected); - captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid); - captureArgs.grayscale = - env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale); -} - -static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env, - jobject displayCaptureArgsObject) { - DisplayCaptureArgs captureArgs; - getCaptureArgs(env, displayCaptureArgsObject, captureArgs); - - captureArgs.displayToken = - ibinderForJavaObject(env, - env->GetObjectField(displayCaptureArgsObject, - gDisplayCaptureArgsClassInfo.displayToken)); - captureArgs.width = - env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width); - captureArgs.height = - env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height); - captureArgs.useIdentityTransform = - env->GetBooleanField(displayCaptureArgsObject, - gDisplayCaptureArgsClassInfo.useIdentityTransform); - return captureArgs; -} - -static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject, - jobject screenCaptureListenerObject) { - const DisplayCaptureArgs captureArgs = - displayCaptureArgsFromObject(env, displayCaptureArgsObject); - - if (captureArgs.displayToken == NULL) { - return BAD_VALUE; - } - - sp<IScreenCaptureListener> captureListener = - new ScreenCaptureListenerWrapper(env, screenCaptureListenerObject); - return ScreenshotClient::captureDisplay(captureArgs, captureListener); -} - -static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject, - jobject screenCaptureListenerObject) { - LayerCaptureArgs captureArgs; - getCaptureArgs(env, layerCaptureArgsObject, captureArgs); - SurfaceControl* layer = reinterpret_cast<SurfaceControl*>( - env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer)); - if (layer == nullptr) { - return BAD_VALUE; - } - - captureArgs.layerHandle = layer->getHandle(); - captureArgs.childrenOnly = - env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly); - jlongArray excludeObjectArray = reinterpret_cast<jlongArray>( - env->GetObjectField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.excludeLayers)); - if (excludeObjectArray != NULL) { - const jsize len = env->GetArrayLength(excludeObjectArray); - captureArgs.excludeHandles.reserve(len); - - const jlong* objects = env->GetLongArrayElements(excludeObjectArray, nullptr); - for (jsize i = 0; i < len; i++) { - auto excludeObject = reinterpret_cast<SurfaceControl *>(objects[i]); - if (excludeObject == nullptr) { - jniThrowNullPointerException(env, "Exclude layer is null"); - return NULL; - } - captureArgs.excludeHandles.emplace(excludeObject->getHandle()); - } - env->ReleaseLongArrayElements(excludeObjectArray, const_cast<jlong*>(objects), JNI_ABORT); - } - - sp<IScreenCaptureListener> captureListener = - new ScreenCaptureListenerWrapper(env, screenCaptureListenerObject); - return ScreenshotClient::captureLayers(captureArgs, captureListener); -} - static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); transaction->apply(sync); @@ -664,12 +455,12 @@ static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, j Rect source, dst; if (sourceObj != NULL) { - source = rectFromObj(env, sourceObj); + source = JNICommon::rectFromObj(env, sourceObj); } else { source.makeInvalid(); } if (dstObj != NULL) { - dst = rectFromObj(env, dstObj); + dst = JNICommon::rectFromObj(env, dstObj); } else { dst.makeInvalid(); } @@ -1183,20 +974,6 @@ static jobject nativeGetDisplayedContentSample(JNIEnv* env, jclass clazz, jobjec histogramComponent3); } -static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, - jboolean secure) { - ScopedUtfChars name(env, nameObj); - sp<IBinder> token(SurfaceComposerClient::createDisplay( - String8(name.c_str()), bool(secure))); - return javaObjectForIBinder(env, token); -} - -static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { - sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); - if (token == NULL) return; - SurfaceComposerClient::destroyDisplay(token); -} - static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz, jlong transactionObj, jobject tokenObj, jlong nativeSurfaceObject) { @@ -2210,10 +1987,6 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetPhysicalDisplayIds }, {"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;", (void*)nativeGetPhysicalDisplayToken }, - {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;", - (void*)nativeCreateDisplay }, - {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", - (void*)nativeDestroyDisplay }, {"nativeSetDisplaySurface", "(JLandroid/os/IBinder;J)V", (void*)nativeSetDisplaySurface }, {"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V", @@ -2269,12 +2042,6 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetProtectedContentSupport }, {"nativeReparent", "(JJJ)V", (void*)nativeReparent }, - {"nativeCaptureDisplay", - "(Landroid/view/SurfaceControl$DisplayCaptureArgs;Landroid/view/SurfaceControl$ScreenCaptureListener;)I", - (void*)nativeCaptureDisplay }, - {"nativeCaptureLayers", - "(Landroid/view/SurfaceControl$LayerCaptureArgs;Landroid/view/SurfaceControl$ScreenCaptureListener;)I", - (void*)nativeCaptureLayers }, {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V", (void*)nativeSetInputWindowInfo }, {"nativeSetMetadata", "(JJILandroid/os/Parcel;)V", @@ -2416,12 +2183,6 @@ int register_android_view_SurfaceControl(JNIEnv* env) GetFieldIDOrDie(env, modeClazz, "presentationDeadlineNanos", "J"); gDisplayModeClassInfo.group = GetFieldIDOrDie(env, modeClazz, "group", "I"); - jclass rectClazz = FindClassOrDie(env, "android/graphics/Rect"); - gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClazz, "bottom", "I"); - gRectClassInfo.left = GetFieldIDOrDie(env, rectClazz, "left", "I"); - gRectClassInfo.right = GetFieldIDOrDie(env, rectClazz, "right", "I"); - gRectClassInfo.top = GetFieldIDOrDie(env, rectClazz, "top", "I"); - jclass frameStatsClazz = FindClassOrDie(env, "android/view/FrameStats"); jfieldID undefined_time_nano_field = GetStaticFieldIDOrDie(env, frameStatsClazz, "UNDEFINED_TIME_NANO", "J"); @@ -2462,15 +2223,6 @@ int register_android_view_SurfaceControl(JNIEnv* env) GetMethodIDOrDie(env, deviceProductInfoManufactureDateClazz, "<init>", "(Ljava/lang/Integer;Ljava/lang/Integer;)V"); - jclass screenshotGraphicsBufferClazz = - FindClassOrDie(env, "android/view/SurfaceControl$ScreenshotHardwareBuffer"); - gScreenshotHardwareBufferClassInfo.clazz = - MakeGlobalRefOrDie(env, screenshotGraphicsBufferClazz); - gScreenshotHardwareBufferClassInfo.builder = - GetStaticMethodIDOrDie(env, screenshotGraphicsBufferClazz, "createFromNative", - "(Landroid/hardware/HardwareBuffer;IZZ)Landroid/view/" - "SurfaceControl$ScreenshotHardwareBuffer;"); - jclass displayedContentSampleClazz = FindClassOrDie(env, "android/hardware/display/DisplayedContentSample"); gDisplayedContentSampleClassInfo.clazz = MakeGlobalRefOrDie(env, displayedContentSampleClazz); @@ -2523,46 +2275,6 @@ int register_android_view_SurfaceControl(JNIEnv* env) gDesiredDisplayModeSpecsClassInfo.appRequestRefreshRateMax = GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "appRequestRefreshRateMax", "F"); - jclass captureArgsClazz = FindClassOrDie(env, "android/view/SurfaceControl$CaptureArgs"); - gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I"); - gCaptureArgsClassInfo.sourceCrop = - GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;"); - gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F"); - gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F"); - gCaptureArgsClassInfo.captureSecureLayers = - GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z"); - gCaptureArgsClassInfo.allowProtected = - GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z"); - gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J"); - gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z"); - - jclass displayCaptureArgsClazz = - FindClassOrDie(env, "android/view/SurfaceControl$DisplayCaptureArgs"); - gDisplayCaptureArgsClassInfo.displayToken = - GetFieldIDOrDie(env, displayCaptureArgsClazz, "mDisplayToken", "Landroid/os/IBinder;"); - gDisplayCaptureArgsClassInfo.width = - GetFieldIDOrDie(env, displayCaptureArgsClazz, "mWidth", "I"); - gDisplayCaptureArgsClassInfo.height = - GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I"); - gDisplayCaptureArgsClassInfo.useIdentityTransform = - GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z"); - - jclass layerCaptureArgsClazz = - FindClassOrDie(env, "android/view/SurfaceControl$LayerCaptureArgs"); - gLayerCaptureArgsClassInfo.layer = - GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeLayer", "J"); - gLayerCaptureArgsClassInfo.excludeLayers = - GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeExcludeLayers", "[J"); - gLayerCaptureArgsClassInfo.childrenOnly = - GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z"); - - jclass screenCaptureListenerClazz = - FindClassOrDie(env, "android/view/SurfaceControl$ScreenCaptureListener"); - gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz); - gScreenCaptureListenerClassInfo.onScreenCaptureComplete = - GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete", - "(Landroid/view/SurfaceControl$ScreenshotHardwareBuffer;)V"); - jclass jankDataClazz = FindClassOrDie(env, "android/view/SurfaceControl$JankData"); gJankDataClassInfo.clazz = MakeGlobalRefOrDie(env, jankDataClazz); diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp new file mode 100644 index 000000000000..3bada15aa834 --- /dev/null +++ b/core/jni/android_window_ScreenCapture.cpp @@ -0,0 +1,315 @@ +/* + * 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. + */ + +#define LOG_TAG "ScreenCapture" +// #define LOG_NDEBUG 0 + +#include <android/gui/BnScreenCaptureListener.h> +#include <android_runtime/android_hardware_HardwareBuffer.h> +#include <gui/SurfaceComposerClient.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <ui/GraphicTypes.h> + +#include "android_os_Parcel.h" +#include "android_util_Binder.h" +#include "core_jni_helpers.h" +#include "jni_common.h" + +// ---------------------------------------------------------------------------- + +namespace android { + +using gui::CaptureArgs; + +static struct { + jfieldID pixelFormat; + jfieldID sourceCrop; + jfieldID frameScaleX; + jfieldID frameScaleY; + jfieldID captureSecureLayers; + jfieldID allowProtected; + jfieldID uid; + jfieldID grayscale; +} gCaptureArgsClassInfo; + +static struct { + jfieldID displayToken; + jfieldID width; + jfieldID height; + jfieldID useIdentityTransform; +} gDisplayCaptureArgsClassInfo; + +static struct { + jfieldID layer; + jfieldID excludeLayers; + jfieldID childrenOnly; +} gLayerCaptureArgsClassInfo; + +static struct { + jclass clazz; + jmethodID onScreenCaptureComplete; +} gScreenCaptureListenerClassInfo; + +static struct { + jclass clazz; + jmethodID builder; +} gScreenshotHardwareBufferClassInfo; + +enum JNamedColorSpace : jint { + // ColorSpace.Named.SRGB.ordinal() = 0; + SRGB = 0, + + // ColorSpace.Named.DISPLAY_P3.ordinal() = 7; + DISPLAY_P3 = 7, +}; + +constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace) { + switch (dataspace) { + case ui::Dataspace::DISPLAY_P3: + return JNamedColorSpace::DISPLAY_P3; + default: + return JNamedColorSpace::SRGB; + } +} + +static void checkAndClearException(JNIEnv* env, const char* methodName) { + if (env->ExceptionCheck()) { + ALOGE("An exception was thrown by callback '%s'.", methodName); + env->ExceptionClear(); + } +} + +class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener { +public: + explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + mScreenCaptureListenerObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref"); + } + + ~ScreenCaptureListenerWrapper() { + if (mScreenCaptureListenerObject) { + getenv()->DeleteGlobalRef(mScreenCaptureListenerObject); + mScreenCaptureListenerObject = nullptr; + } + } + + binder::Status onScreenCaptureCompleted( + const gui::ScreenCaptureResults& captureResults) override { + JNIEnv* env = getenv(); + if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) { + env->CallVoidMethod(mScreenCaptureListenerObject, + gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr); + checkAndClearException(env, "onScreenCaptureComplete"); + return binder::Status::ok(); + } + captureResults.fenceResult.value()->waitForever(LOG_TAG); + jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( + env, captureResults.buffer->toAHardwareBuffer()); + const jint namedColorSpace = + fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace); + jobject screenshotHardwareBuffer = + env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, + gScreenshotHardwareBufferClassInfo.builder, + jhardwareBuffer, namedColorSpace, + captureResults.capturedSecureLayers, + captureResults.capturedHdrLayers); + checkAndClearException(env, "builder"); + env->CallVoidMethod(mScreenCaptureListenerObject, + gScreenCaptureListenerClassInfo.onScreenCaptureComplete, + screenshotHardwareBuffer); + checkAndClearException(env, "onScreenCaptureComplete"); + env->DeleteLocalRef(jhardwareBuffer); + env->DeleteLocalRef(screenshotHardwareBuffer); + return binder::Status::ok(); + } + +private: + jobject mScreenCaptureListenerObject; + JavaVM* mVm; + + JNIEnv* getenv() { + JNIEnv* env; + if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (mVm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; + } +}; + +static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) { + captureArgs.pixelFormat = static_cast<ui::PixelFormat>( + env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat)); + captureArgs.sourceCrop = + JNICommon::rectFromObj(env, + env->GetObjectField(captureArgsObject, + gCaptureArgsClassInfo.sourceCrop)); + captureArgs.frameScaleX = + env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX); + captureArgs.frameScaleY = + env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY); + captureArgs.captureSecureLayers = + env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers); + captureArgs.allowProtected = + env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected); + captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid); + captureArgs.grayscale = + env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale); +} + +static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env, + jobject displayCaptureArgsObject) { + DisplayCaptureArgs captureArgs; + getCaptureArgs(env, displayCaptureArgsObject, captureArgs); + + captureArgs.displayToken = + ibinderForJavaObject(env, + env->GetObjectField(displayCaptureArgsObject, + gDisplayCaptureArgsClassInfo.displayToken)); + captureArgs.width = + env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width); + captureArgs.height = + env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height); + captureArgs.useIdentityTransform = + env->GetBooleanField(displayCaptureArgsObject, + gDisplayCaptureArgsClassInfo.useIdentityTransform); + return captureArgs; +} + +static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject, + jobject screenCaptureListenerObject) { + const DisplayCaptureArgs captureArgs = + displayCaptureArgsFromObject(env, displayCaptureArgsObject); + + if (captureArgs.displayToken == nullptr) { + return BAD_VALUE; + } + + sp<IScreenCaptureListener> captureListener = + sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject); + return ScreenshotClient::captureDisplay(captureArgs, captureListener); +} + +static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject, + jobject screenCaptureListenerObject) { + LayerCaptureArgs captureArgs; + getCaptureArgs(env, layerCaptureArgsObject, captureArgs); + SurfaceControl* layer = reinterpret_cast<SurfaceControl*>( + env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer)); + if (layer == nullptr) { + return BAD_VALUE; + } + + captureArgs.layerHandle = layer->getHandle(); + captureArgs.childrenOnly = + env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly); + + jlongArray excludeObjectArray = reinterpret_cast<jlongArray>( + env->GetObjectField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.excludeLayers)); + if (excludeObjectArray != nullptr) { + ScopedLongArrayRO excludeArray(env, excludeObjectArray); + const jsize len = excludeArray.size(); + captureArgs.excludeHandles.reserve(len); + + for (jsize i = 0; i < len; i++) { + auto excludeObject = reinterpret_cast<SurfaceControl*>(excludeArray[i]); + if (excludeObject == nullptr) { + jniThrowNullPointerException(env, "Exclude layer is null"); + return BAD_VALUE; + } + captureArgs.excludeHandles.emplace(excludeObject->getHandle()); + } + } + + sp<IScreenCaptureListener> captureListener = + sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject); + return ScreenshotClient::captureLayers(captureArgs, captureListener); +} + +// ---------------------------------------------------------------------------- + +static const JNINativeMethod sScreenCaptureMethods[] = { + // clang-format off + {"nativeCaptureDisplay", + "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I", + (void*)nativeCaptureDisplay }, + {"nativeCaptureLayers", + "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I", + (void*)nativeCaptureLayers }, + // clang-format on +}; + +int register_android_window_ScreenCapture(JNIEnv* env) { + int err = RegisterMethodsOrDie(env, "android/window/ScreenCapture", sScreenCaptureMethods, + NELEM(sScreenCaptureMethods)); + + jclass captureArgsClazz = FindClassOrDie(env, "android/window/ScreenCapture$CaptureArgs"); + gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I"); + gCaptureArgsClassInfo.sourceCrop = + GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;"); + gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F"); + gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F"); + gCaptureArgsClassInfo.captureSecureLayers = + GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z"); + gCaptureArgsClassInfo.allowProtected = + GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z"); + gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J"); + gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z"); + + jclass displayCaptureArgsClazz = + FindClassOrDie(env, "android/window/ScreenCapture$DisplayCaptureArgs"); + gDisplayCaptureArgsClassInfo.displayToken = + GetFieldIDOrDie(env, displayCaptureArgsClazz, "mDisplayToken", "Landroid/os/IBinder;"); + gDisplayCaptureArgsClassInfo.width = + GetFieldIDOrDie(env, displayCaptureArgsClazz, "mWidth", "I"); + gDisplayCaptureArgsClassInfo.height = + GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I"); + gDisplayCaptureArgsClassInfo.useIdentityTransform = + GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z"); + + jclass layerCaptureArgsClazz = + FindClassOrDie(env, "android/window/ScreenCapture$LayerCaptureArgs"); + gLayerCaptureArgsClassInfo.layer = + GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeLayer", "J"); + gLayerCaptureArgsClassInfo.excludeLayers = + GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeExcludeLayers", "[J"); + gLayerCaptureArgsClassInfo.childrenOnly = + GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z"); + + jclass screenCaptureListenerClazz = + FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener"); + gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz); + gScreenCaptureListenerClassInfo.onScreenCaptureComplete = + GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete", + "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V"); + + jclass screenshotGraphicsBufferClazz = + FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer"); + gScreenshotHardwareBufferClassInfo.clazz = + MakeGlobalRefOrDie(env, screenshotGraphicsBufferClazz); + gScreenshotHardwareBufferClassInfo.builder = + GetStaticMethodIDOrDie(env, screenshotGraphicsBufferClazz, "createFromNative", + "(Landroid/hardware/HardwareBuffer;IZZ)Landroid/window/" + "ScreenCapture$ScreenshotHardwareBuffer;"); + + return err; +} + +} // namespace android diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index 5b946d5c8d3a..a95b6e37f5de 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -244,6 +244,38 @@ static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get()); } +static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, + jlongArray jarray, jintArray jindexMap) { + std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); + ScopedLongArrayRW scopedArray(env, jarray); + ScopedIntArrayRO scopedIndexMap(env, jindexMap); + + const uint64_t *data = vector->data(); + uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get()); + const uint8_t size = scopedArray.size(); + + for (int i = 0; i < size; i++) { + array[i] = 0; + } + + bool nonZero = false; + for (int i = 0; i < vector->size(); i++) { + jint index = scopedIndexMap[i]; + if (index < 0 || index >= size) { + jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException", + "Index %d is out of bounds: [0, %d]", index, size - 1); + return false; + } + + if (data[i] != 0L) { + array[index] += data[i]; + nonZero = true; + } + } + + return nonZero; +} + static const JNINativeMethod g_LongArrayContainer_methods[] = { // @CriticalNative {"native_init", "(I)J", (void *)native_init_LongArrayContainer}, @@ -253,6 +285,8 @@ static const JNINativeMethod g_LongArrayContainer_methods[] = { {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer}, // @FastNative {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer}, + // @FastNative + {"native_combineValues", "(J[J[I)Z", (void *)native_combineValues_LongArrayContainer}, }; int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) { diff --git a/core/jni/jni_common.cpp b/core/jni/jni_common.cpp new file mode 100644 index 000000000000..8d376cf2c7f9 --- /dev/null +++ b/core/jni/jni_common.cpp @@ -0,0 +1,52 @@ +/* + * 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. + */ +#include "jni_common.h" + +#include <jni.h> +#include <ui/GraphicTypes.h> +#include <ui/Rect.h> + +#include "core_jni_helpers.h" + +// ---------------------------------------------------------------------------- + +namespace android { + +static struct { + jfieldID bottom; + jfieldID left; + jfieldID right; + jfieldID top; +} gRectClassInfo; + +Rect JNICommon::rectFromObj(JNIEnv* env, jobject rectObj) { + int left = env->GetIntField(rectObj, gRectClassInfo.left); + int top = env->GetIntField(rectObj, gRectClassInfo.top); + int right = env->GetIntField(rectObj, gRectClassInfo.right); + int bottom = env->GetIntField(rectObj, gRectClassInfo.bottom); + return Rect(left, top, right, bottom); +} + +int register_jni_common(JNIEnv* env) { + jclass rectClazz = FindClassOrDie(env, "android/graphics/Rect"); + gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClazz, "bottom", "I"); + gRectClassInfo.left = GetFieldIDOrDie(env, rectClazz, "left", "I"); + gRectClassInfo.right = GetFieldIDOrDie(env, rectClazz, "right", "I"); + gRectClassInfo.top = GetFieldIDOrDie(env, rectClazz, "top", "I"); + return 0; +} + +} // namespace android diff --git a/core/jni/jni_common.h b/core/jni/jni_common.h new file mode 100644 index 000000000000..a2bf6fb3f5e0 --- /dev/null +++ b/core/jni/jni_common.h @@ -0,0 +1,26 @@ +/* + * 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. + */ +#include <jni.h> + +namespace android { + +class Rect; + +class JNICommon { +public: + static Rect rectFromObj(JNIEnv* env, jobject rectObj); +}; +} // namespace android
\ No newline at end of file diff --git a/core/res/OWNERS b/core/res/OWNERS index d8fc2181cfe8..22f40a1461c0 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -36,6 +36,9 @@ per-file res/xml/config_user_types.xml = file:/MULTIUSER_OWNERS # Car per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNERS +# Device Idle +per-file res/values/config_device_idle.xml = file:/apex/jobscheduler/OWNERS + # Wear per-file res/*-watch/* = file:/WEAR_OWNERS diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dfada139b4fe..32494ef25ca8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5191,7 +5191,7 @@ <!-- Vertical position of a center of the letterboxed app window. 0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0 or > 1, it is ignored and central position is used (0.5). --> - <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.5</item> + <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.0</item> <!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps. --> diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml new file mode 100644 index 000000000000..8ed58f326c2d --- /dev/null +++ b/core/res/res/values/config_device_idle.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2022, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. Do not translate. + + NOTE: The naming convention is "config_camelCaseValue". Some legacy + entries do not follow the convention, but all new entries should. --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Default for DeviceIdleController.Constants.FLEX_TIME_SHORT --> + <integer name="device_idle_flex_time_short_ms">60000</integer> + + <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT --> + <integer name="device_idle_light_after_inactive_to_ms">180000</integer> + + <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_TIMEOUT --> + <integer name="device_idle_light_idle_to_ms">300000</integer> + + <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_FACTOR --> + <item name="device_idle_light_idle_factor" format="float" type="integer">2.0</item> + + <!-- Default for DeviceIdleController.Constants.LIGHT_MAX_IDLE_TIMEOUT --> + <integer name="device_idle_light_max_idle_to_ms">900000</integer> + + <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET --> + <integer name="device_idle_light_idle_maintenance_min_budget_ms">60000</integer> + + <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET --> + <integer name="device_idle_light_idle_maintenance_max_budget_ms">300000</integer> + + <!-- Default for DeviceIdleController.Constants.MIN_LIGHT_MAINTENANCE_TIME --> + <integer name="device_idle_min_light_maintenance_time_ms">5000</integer> + + <!-- Default for DeviceIdleController.Constants.MIN_DEEP_MAINTENANCE_TIME --> + <integer name="device_idle_min_deep_maintenance_time_ms">30000</integer> + + <!-- Default for DeviceIdleController.Constants.INACTIVE_TIMEOUT --> + <integer name="device_idle_inactive_to_ms">1800000</integer> + + <!-- Default for DeviceIdleController.Constants.SENSING_TIMEOUT --> + <integer name="device_idle_sensing_to_ms">240000</integer> + + <!-- Default for DeviceIdleController.Constants.LOCATING_TIMEOUT --> + <integer name="device_idle_locating_to_ms">30000</integer> + + <!-- Default for DeviceIdleController.Constants.LOCATION_ACCURACY --> + <item name="device_idle_location_accuracy" format="float" type="integer">20.0</item> + + <!-- Default for DeviceIdleController.Constants.MOTION_INACTIVE_TIMEOUT --> + <integer name="device_idle_motion_inactive_to_ms">600000</integer> + + <!-- Default for DeviceIdleController.Constants.MOTION_INACTIVE_TIMEOUT_FLEX --> + <integer name="device_idle_motion_inactive_to_flex_ms">60000</integer> + + <!-- Default for DeviceIdleController.Constants.IDLE_AFTER_INACTIVE_TIMEOUT --> + <integer name="device_idle_idle_after_inactive_to_ms">1800000</integer> + + <!-- Default for DeviceIdleController.Constants.IDLE_PENDING_TIMEOUT --> + <integer name="device_idle_idle_pending_to_ms">300000</integer> + + <!-- Default for DeviceIdleController.Constants.MAX_IDLE_PENDING_TIMEOUT --> + <integer name="device_idle_max_idle_pending_to_ms">600000</integer> + + <!-- Default for DeviceIdleController.Constants.IDLE_PENDING_FACTOR --> + <item name="device_idle_idle_pending_factor" format="float" type="integer">2.0</item> + + <!-- Default for DeviceIdleController.Constants.QUICK_DOZE_DELAY_TIMEOUT --> + <integer name="device_idle_quick_doze_delay_to_ms">60000</integer> + + <!-- Default for DeviceIdleController.Constants.IDLE_TIMEOUT --> + <integer name="device_idle_idle_to_ms">3600000</integer> + + <!-- Default for DeviceIdleController.Constants.MAX_IDLE_TIMEOUT --> + <integer name="device_idle_max_idle_to_ms">21600000</integer> + + <!-- Default for DeviceIdleController.Constants.IDLE_FACTOR --> + <item name="device_idle_idle_factor" format="float" type="integer">2.0</item> + + <!-- Default for DeviceIdleController.Constants.MIN_TIME_TO_ALARM --> + <integer name="device_idle_min_time_to_alarm_ms">3600000</integer> + + <!-- Default for DeviceIdleController.Constants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS --> + <integer name="device_idle_max_temp_app_allowlist_duration_ms">300000</integer> + + <!-- Default for DeviceIdleController.Constants.MMS_TEMP_APP_ALLOWLIST_DURATION_MS --> + <integer name="device_idle_mms_temp_app_allowlist_duration_ms">60000</integer> + + <!-- Default for DeviceIdleController.Constants.SMS_TEMP_APP_ALLOWLIST_DURATION_MS --> + <integer name="device_idle_sms_temp_app_allowlist_duration_ms">20000</integer> + + <!-- Default for DeviceIdleController.Constants.NOTIFICATION_ALLOWLIST_DURATION_MS --> + <integer name="device_idle_notification_allowlist_duration_ms">30000</integer> + + <!-- Default for DeviceIdleController.Constants.WAIT_FOR_UNLOCK --> + <bool name="device_idle_wait_for_unlock">true</bool> + + <!-- Default for DeviceIdleController.Constants.PRE_IDLE_FACTOR_LONG --> + <item name="device_idle_pre_idle_factor_long" format="float" type="integer">1.67</item> + + <!-- Default for DeviceIdleController.Constants.PRE_IDLE_FACTOR_SHORT --> + <item name="device_idle_pre_idle_factor_short" format="float" type="integer">0.33</item> + + <!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS --> + <bool name="device_idle_use_window_alarms">true</bool> +</resources> + diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 1327d9604001..ea2b988bd237 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -17,12 +17,6 @@ <resources> <!-- This file defines Android telephony related resources --> - <!-- Whether force disabling telephony new data stack or not. - This flag and the old data stack code will be deleted in Android 14. - --> - <bool name="config_force_disable_telephony_new_data_stack">false</bool> - <java-symbol type="bool" name="config_force_disable_telephony_new_data_stack" /> - <!-- Configure tcp buffer sizes per network type in the form: network-type:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 63eb83eb8db0..9214f438826f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1701,6 +1701,8 @@ <string name="fingerprint_acquired_already_enrolled">Try another fingerprint</string> <!-- Message shown during fingerprint acquisition when fingerprint sensor detected too much light.[CHAR LIMIT=50] --> <string name="fingerprint_acquired_too_bright">Too bright</string> + <!-- Message shown during fingerprint acquisition when a Power press has been detected.[CHAR LIMIT=50] --> + <string name="fingerprint_acquired_power_press">Power press detected</string> <!-- Message shown during fingerprint acquisition when a fingerprint must be adjusted.[CHAR LIMIT=50] --> <string name="fingerprint_acquired_try_adjusting">Try adjusting</string> <!-- Message shown during fingerprint acquisition when a fingeprint area has already been captured during enrollment [CHAR LIMIT=100] --> @@ -3574,11 +3576,11 @@ <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button is pressed during fingerprint enrollment. --> - <string name="fp_power_button_enrollment_title">Tap to turn off screen</string> + <string name="fp_power_button_enrollment_title">To end setup, turn off screen</string> <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the power button is pressed during fingerprint enrollment. --> - <string name="fp_power_button_enrollment_button_text">Turn off screen</string> + <string name="fp_power_button_enrollment_button_text">Turn off</string> <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button is pressed during biometric prompt when a side fingerprint sensor is present. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 66bfbb61ff15..7e8423a2d284 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2611,6 +2611,7 @@ <java-symbol type="string" name="fingerprint_acquired_imager_dirty" /> <java-symbol type="string" name="fingerprint_acquired_too_slow" /> <java-symbol type="string" name="fingerprint_acquired_too_fast" /> + <java-symbol type="string" name="fingerprint_acquired_power_press" /> <java-symbol type="string" name="fingerprint_acquired_too_bright" /> <java-symbol type="array" name="fingerprint_acquired_vendor" /> <java-symbol type="string" name="fingerprint_error_canceled" /> @@ -4413,6 +4414,40 @@ <java-symbol type="array" name="config_notificationMsgPkgsAllowedAsConvos" /> + <!-- To config device idle --> + <java-symbol type="integer" name="device_idle_flex_time_short_ms" /> + <java-symbol type="integer" name="device_idle_light_after_inactive_to_ms" /> + <java-symbol type="integer" name="device_idle_light_idle_to_ms" /> + <java-symbol type="integer" name="device_idle_light_idle_factor" /> + <java-symbol type="integer" name="device_idle_light_max_idle_to_ms" /> + <java-symbol type="integer" name="device_idle_light_idle_maintenance_min_budget_ms" /> + <java-symbol type="integer" name="device_idle_light_idle_maintenance_max_budget_ms" /> + <java-symbol type="integer" name="device_idle_min_light_maintenance_time_ms" /> + <java-symbol type="integer" name="device_idle_min_deep_maintenance_time_ms" /> + <java-symbol type="integer" name="device_idle_inactive_to_ms" /> + <java-symbol type="integer" name="device_idle_sensing_to_ms" /> + <java-symbol type="integer" name="device_idle_locating_to_ms" /> + <java-symbol type="integer" name="device_idle_location_accuracy" /> + <java-symbol type="integer" name="device_idle_motion_inactive_to_ms" /> + <java-symbol type="integer" name="device_idle_motion_inactive_to_flex_ms" /> + <java-symbol type="integer" name="device_idle_idle_after_inactive_to_ms" /> + <java-symbol type="integer" name="device_idle_idle_pending_to_ms" /> + <java-symbol type="integer" name="device_idle_max_idle_pending_to_ms" /> + <java-symbol type="integer" name="device_idle_idle_pending_factor" /> + <java-symbol type="integer" name="device_idle_quick_doze_delay_to_ms" /> + <java-symbol type="integer" name="device_idle_idle_to_ms" /> + <java-symbol type="integer" name="device_idle_max_idle_to_ms" /> + <java-symbol type="integer" name="device_idle_idle_factor" /> + <java-symbol type="integer" name="device_idle_min_time_to_alarm_ms" /> + <java-symbol type="integer" name="device_idle_max_temp_app_allowlist_duration_ms" /> + <java-symbol type="integer" name="device_idle_mms_temp_app_allowlist_duration_ms" /> + <java-symbol type="integer" name="device_idle_sms_temp_app_allowlist_duration_ms" /> + <java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" /> + <java-symbol type="bool" name="device_idle_wait_for_unlock" /> + <java-symbol type="integer" name="device_idle_pre_idle_factor_long" /> + <java-symbol type="integer" name="device_idle_pre_idle_factor_short" /> + <java-symbol type="bool" name="device_idle_use_window_alarms" /> + <!-- Binder heavy hitter watcher configs --> <java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" /> <java-symbol type="integer" name="config_defaultBinderHeavyHitterWatcherBatchSize" /> diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java new file mode 100644 index 000000000000..7248983c741c --- /dev/null +++ b/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.ddm; + +import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters; +import static android.ddm.DdmHandleViewDebug.serializeReturnValue; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public final class DdmHandleViewDebugTest { + // true + private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1}; + + @Test + public void serializeReturnValue_booleanTrue() throws Exception { + assertArrayEquals(SERIALIZED_BOOLEAN_TRUE, serializeReturnValue(boolean.class, true)); + } + + @Test + public void deserializeMethodParameters_booleanTrue() throws Exception { + expectDeserializedArgument(boolean.class, true, SERIALIZED_BOOLEAN_TRUE); + } + + // false + private static final byte[] SERIALIZED_BOOLEAN_FALSE = {0x00, 0x5A, 0}; + + @Test + public void serializeReturnValue_booleanFalse() throws Exception { + assertArrayEquals(SERIALIZED_BOOLEAN_FALSE, serializeReturnValue(boolean.class, false)); + } + + @Test + public void deserializeMethodParameters_booleanFalse() throws Exception { + expectDeserializedArgument(boolean.class, false, SERIALIZED_BOOLEAN_FALSE); + } + + // (byte) 42 + private static final byte[] SERIALIZED_BYTE = {0x00, 0x42, 42}; + + @Test + public void serializeReturnValue_byte() throws Exception { + assertArrayEquals(SERIALIZED_BYTE, serializeReturnValue(byte.class, (byte) 42)); + } + + @Test + public void deserializeMethodParameters_byte() throws Exception { + expectDeserializedArgument(byte.class, (byte) 42, SERIALIZED_BYTE); + } + + // '\u1122' + private static final byte[] SERIALIZED_CHAR = {0x00, 0x43, 0x11, 0x22}; + + @Test + public void serializeReturnValue_char() throws Exception { + assertArrayEquals(SERIALIZED_CHAR, serializeReturnValue(char.class, '\u1122')); + } + + @Test + public void deserializeMethodParameters_char() throws Exception { + expectDeserializedArgument(char.class, '\u1122', SERIALIZED_CHAR); + } + + // (short) 0x1011 + private static final byte[] SERIALIZED_SHORT = {0x00, 0x53, 0x10, 0x11}; + + @Test + public void serializeReturnValue_short() throws Exception { + assertArrayEquals(SERIALIZED_SHORT, + serializeReturnValue(short.class, (short) 0x1011)); + } + + @Test + public void deserializeMethodParameters_short() throws Exception { + expectDeserializedArgument(short.class, (short) 0x1011, SERIALIZED_SHORT); + } + + // 0x11223344 + private static final byte[] SERIALIZED_INT = {0x00, 0x49, 0x11, 0x22, 0x33, 0x44}; + + @Test + public void serializeReturnValue_int() throws Exception { + assertArrayEquals(SERIALIZED_INT, + serializeReturnValue(int.class, 0x11223344)); + } + + @Test + public void deserializeMethodParameters_int() throws Exception { + expectDeserializedArgument(int.class, 0x11223344, SERIALIZED_INT); + } + + // 0x0011223344556677L + private static final byte[] SERIALIZED_LONG = + {0x00, 0x4a, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + + @Test + public void serializeReturnValue_long() throws Exception { + assertArrayEquals(SERIALIZED_LONG, + serializeReturnValue(long.class, 0x0011223344556677L)); + } + + @Test + public void deserializeMethodParameters_long() throws Exception { + expectDeserializedArgument(long.class, 0x0011223344556677L, SERIALIZED_LONG); + } + + // 3.141d + private static final byte[] SERIALIZED_DOUBLE = + {0x00, 0x44, (byte) 0x40, (byte) 0x09, (byte) 0x20, (byte) 0xc4, (byte) 0x9b, + (byte) 0xa5, (byte) 0xe3, (byte) 0x54}; + + @Test + public void serializeReturnValue_double() throws Exception { + assertArrayEquals( + SERIALIZED_DOUBLE, + serializeReturnValue(double.class, 3.141d)); + } + + @Test + public void deserializeMethodParameters_double() throws Exception { + expectDeserializedArgument(double.class, 3.141d, SERIALIZED_DOUBLE); + } + + // 3.141f + private static final byte[] SERIALIZED_FLOAT = + {0x00, 0x46, (byte) 0x40, (byte) 0x49, (byte) 0x06, (byte) 0x25}; + + @Test + public void serializeReturnValue_float() throws Exception { + assertArrayEquals(SERIALIZED_FLOAT, + serializeReturnValue(float.class, 3.141f)); + } + + @Test + public void deserializeMethodParameters_float() throws Exception { + expectDeserializedArgument(float.class, 3.141f, SERIALIZED_FLOAT); + } + + // "foo" + private static final byte[] SERIALIZED_ASCII_STRING = {0x00, 0x52, 0, 3, 0x66, 0x6f, 0x6f}; + + @Test + public void serializeReturnValue_asciiString() throws Exception { + assertArrayEquals(SERIALIZED_ASCII_STRING, + serializeReturnValue(String.class, "foo")); + } + + @Test + public void deserializeMethodParameters_asciiString() throws Exception { + expectDeserializedArgument(String.class, "foo", SERIALIZED_ASCII_STRING); + } + + // "\u1122" + private static final byte[] SERIALIZED_NON_ASCII_STRING = + {0x00, 0x52, 0, 3, (byte) 0xe1, (byte) 0x84, (byte) 0xa2}; + + @Test + public void serializeReturnValue_nonAsciiString_encodesAsUtf8() throws Exception { + assertArrayEquals(SERIALIZED_NON_ASCII_STRING, + serializeReturnValue(String.class, "\u1122")); + } + + @Test + public void deserializeMethodParameters_decodesFromUtf8() throws Exception { + expectDeserializedArgument(String.class, "\u1122", SERIALIZED_NON_ASCII_STRING); + } + + // "" + private static final byte[] SERIALIZED_EMPTY_STRING = {0x00, 0x52, 0, 0}; + + @Test + public void serializeReturnValue_emptyString() throws Exception { + assertArrayEquals(SERIALIZED_EMPTY_STRING, serializeReturnValue(String.class, "")); + } + + @Test + public void deserializeMethodParameters_emptyString() throws Exception { + expectDeserializedArgument(String.class, "", SERIALIZED_EMPTY_STRING); + } + + @Test + public void serializeReturnValue_nullString_encodesAsEmptyString() throws Exception { + assertArrayEquals(new byte[]{0x00, 0x52, 0, 0}, serializeReturnValue(String.class, null)); + } + + // Illegal - string length exceeding actual bytes + private static final byte[] SERIALIZED_INVALID_STRING = + {0x00, 0x52, 0, 3, 0x66}; + + @Test + public void deserializeMethodParameters_stringPayloadMissing_throws() throws Exception { + Object[] args = new Object[1]; + Class<?>[] argTypes = new Class<?>[1]; + assertThrows(BufferUnderflowException.class, + () -> deserializeMethodParameters(args, argTypes, + ByteBuffer.wrap(SERIALIZED_INVALID_STRING))); + } + + @Test + public void serializeAndDeserialize_handlesStringsUpTo64k() throws Exception { + char[] chars = new char[65535]; + Arrays.fill(chars, 'a'); + String original = new String(chars); + byte[] serialized = serializeReturnValue(String.class, original); + + // 2 bytes for the R signature char, 2 bytes char string byte count, 2^16-1 bytes ASCII + // payload + assertEquals(2 + 2 + 65535, serialized.length); + + // length is unsigned short + assertArrayEquals(new byte[]{0x00, 0x52, (byte) 0xff, (byte) 0xff}, + Arrays.copyOfRange(serialized, 0, 4)); + + // length of string must be interpreted as unsigned short, returning original content + expectDeserializedArgument(String.class, original, serialized); + } + + private static final byte[] SERIALIZED_VOID = {0x00, 0x56}; + + @Test + public void serializeReturnValue_void() throws Exception { + assertArrayEquals(SERIALIZED_VOID, serializeReturnValue(void.class, null)); + } + + @Test + public void deserializeMethodParameters_void_throws() throws Exception { + Object[] args = new Object[1]; + Class<?>[] argTypes = new Class<?>[1]; + assertThrows(ViewMethodInvocationSerializationException.class, + () -> deserializeMethodParameters(args, argTypes, + ByteBuffer.wrap(SERIALIZED_VOID))); + } + + // new byte[]{} + private static final byte[] SERIALIZED_EMPTY_BYTE_ARRAY = {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 0}; + + @Test + public void serializeReturnValue_emptyByteArray() throws Exception { + assertArrayEquals(SERIALIZED_EMPTY_BYTE_ARRAY, + serializeReturnValue(byte[].class, new byte[]{})); + } + + @Test + public void deserializeMethodParameters_emptyByteArray() throws Exception { + expectDeserializedArgument(byte[].class, new byte[]{}, SERIALIZED_EMPTY_BYTE_ARRAY); + } + + // new byte[]{0, 42} + private static final byte[] SERIALIZED_SIMPLE_BYTE_ARRAY = + {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 2, 0, 42}; + + @Test + public void serializeReturnValue_byteArray() throws Exception { + assertArrayEquals(SERIALIZED_SIMPLE_BYTE_ARRAY, + serializeReturnValue(byte[].class, new byte[]{0, 42})); + } + + @Test + public void deserializeMethodParameters_byteArray() throws Exception { + expectDeserializedArgument(byte[].class, new byte[]{0, 42}, SERIALIZED_SIMPLE_BYTE_ARRAY); + } + + @Test + public void serializeReturnValue_largeByteArray_encodesSizeCorrectly() throws Exception { + byte[] result = serializeReturnValue(byte[].class, new byte[0x012233]); + // 2 bytes for the each [Z signature char, 4 bytes int array length, 0x012233 bytes payload + assertEquals(2 + 2 + 4 + 74291, result.length); + + assertArrayEquals(new byte[]{0x00, 0x5b, 0x00, 0x42, 0x00, 0x01, 0x22, 0x33}, + Arrays.copyOfRange(result, 0, 8)); + } + + // Illegal - declared size exceeds remaining buffer length + private static final byte[] SERIALIZED_INVALID_BYTE_ARRAY = + {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 3, 0, 42}; + + @Test + public void deserializeMethodParameters_sizeExceedsBuffer_throws() throws Exception { + Object[] args = new Object[1]; + Class<?>[] argTypes = new Class<?>[1]; + assertThrows(BufferUnderflowException.class, + () -> deserializeMethodParameters(args, argTypes, + ByteBuffer.wrap(SERIALIZED_INVALID_BYTE_ARRAY))); + } + + // new int[]{} + private static final byte[] SERIALIZED_EMPTY_INT_ARRAY = {0x00, 0x5b, 0x00, 0x49, 0, 0, 0, 0}; + + @Test + public void serializeReturnValue_nonByteArrayType_throws() throws Exception { + assertThrows(ViewMethodInvocationSerializationException.class, + () -> serializeReturnValue(int[].class, 42)); + } + + @Test + public void deserializeMethodParameters_nonByteArrayType_throws() throws Exception { + Object[] args = new Object[1]; + Class<?>[] argTypes = new Class<?>[1]; + assertThrows(ViewMethodInvocationSerializationException.class, + () -> deserializeMethodParameters(args, argTypes, + ByteBuffer.wrap(SERIALIZED_EMPTY_INT_ARRAY))); + } + + // new byte[]{0, 42} + private static final byte[] SERIALIZED_MULTIPLE_PARAMETERS = + {0x00, 0x42, 42, 0x00, 0x5A, 1}; + + @Test + public void deserializeMethodParameters_multipleParameters() throws Exception { + expectDeserializedArguments(new Class[]{byte.class, boolean.class}, + new Object[]{(byte) 42, true}, SERIALIZED_MULTIPLE_PARAMETERS); + } + + // Illegal - type 'X' + private static final byte[] SERIALIZED_INVALID_UNKNOWN_TYPE = {0x00, 0x58}; + + @Test + public void deserializeMethodParameters_unknownType_throws() throws Exception { + Object[] args = new Object[1]; + Class<?>[] argTypes = new Class<?>[1]; + assertThrows(ViewMethodInvocationSerializationException.class, + () -> deserializeMethodParameters(args, argTypes, + ByteBuffer.wrap(SERIALIZED_INVALID_UNKNOWN_TYPE))); + } + + @Test + public void deserializeMethodParameters_noArgumentsEmptyPacket_isNoop() throws Exception { + Object[] args = new Object[0]; + Class<?>[] argTypes = new Class<?>[0]; + deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0])); + } + + @Test + public void deserializeMethodParameters_withArgumentsEmptyPacket_throws() throws Exception { + Object[] args = new Object[1]; + Class<?>[] argTypes = new Class<?>[1]; + assertThrows(BufferUnderflowException.class, + () -> deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0]))); + } + + private static void expectDeserializedArgument(Class<?> expectedType, Object expectedValue, + byte[] argumentBuffer) throws Exception { + expectDeserializedArguments(new Class[]{expectedType}, new Object[]{expectedValue}, + argumentBuffer); + } + + private static void expectDeserializedArguments(Class<?>[] expectedTypes, + Object[] expectedValues, byte[] argumentBuffer) throws Exception { + final int argCount = expectedTypes.length; + assertEquals("test helper not used correctly", argCount, expectedValues.length); + Object[] actualArgs = new Object[argCount]; + Class<?>[] actualArgTypes = new Class<?>[argCount]; + + ByteBuffer buffer = ByteBuffer.wrap(argumentBuffer); + deserializeMethodParameters(actualArgs, actualArgTypes, buffer); + + for (int i = 0; i < argCount; i++) { + String context = "argument " + i; + assertEquals(context, expectedTypes[i], actualArgTypes[i]); + if (byte[].class.equals(expectedTypes[i])) { + assertArrayEquals((byte[]) expectedValues[i], (byte[]) actualArgs[i]); + } else { + assertEquals(expectedValues[i], actualArgs[i]); + } + } + } +} diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS new file mode 100644 index 000000000000..c8be1919fb93 --- /dev/null +++ b/core/tests/coretests/src/android/ddm/OWNERS @@ -0,0 +1 @@ +michschn@google.com diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java new file mode 100644 index 000000000000..42629ba41287 --- /dev/null +++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConditionTest { + private static final String CLASS = "android.service.notification.Condition"; + + @Test + public void testLongFields_inConstructors() { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + + // Confirm strings are truncated via short constructor + Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE); + + assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length()); + + // Confirm strings are truncated via long constructor + Condition cond2 = new Condition(longUri, longString, longString, longString, + -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS); + + assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length()); + } + + @Test + public void testLongFields_viaParcel() { + // Set fields via reflection to force them to be long, then parcel and unparcel to make sure + // it gets truncated upon unparcelling. + Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder", + Condition.STATE_TRUE); + + try { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + Field id = Class.forName(CLASS).getDeclaredField("id"); + id.setAccessible(true); + id.set(cond, longUri); + Field summary = Class.forName(CLASS).getDeclaredField("summary"); + summary.setAccessible(true); + summary.set(cond, longString); + Field line1 = Class.forName(CLASS).getDeclaredField("line1"); + line1.setAccessible(true); + line1.set(cond, longString); + Field line2 = Class.forName(CLASS).getDeclaredField("line2"); + line2.setAccessible(true); + line2.set(cond, longString); + } catch (NoSuchFieldException e) { + fail(e.toString()); + } catch (ClassNotFoundException e) { + fail(e.toString()); + } catch (IllegalAccessException e) { + fail(e.toString()); + } + + Parcel parcel = Parcel.obtain(); + cond.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Condition fromParcel = new Condition(parcel); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length()); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java new file mode 100644 index 000000000000..2bb5abefd304 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.inputmethod; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; +import android.text.Editable; +import android.text.InputType; +import android.text.Selection; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.SuggestionSpan; +import android.view.View; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BaseInputConnectionTest { + private static final int[] CURSOR_CAPS_MODES = + new int[] { + InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS, + InputType.TYPE_TEXT_FLAG_CAP_WORDS, + InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + }; + + private BaseInputConnection mBaseInputConnection; + private Editable mEditable; + private View mMockView; + + @Before + public void setUp() throws Exception { + mMockView = new View(InstrumentationRegistry.getInstrumentation().getContext()); + mBaseInputConnection = new BaseInputConnection(mMockView, /*fullEditor=*/ true); + mEditable = mBaseInputConnection.getEditable(); + verifyContent("", 0, 0, -1, -1); + } + + @Test + public void testCommitText_toEditorWithoutSelectionAndComposing() { + // before commit: "|" + // after commit: "text1|" + assertThat(mBaseInputConnection.commitText("text1", 1)).isTrue(); + verifyContent("text1", 5, 5, -1, -1); + + // before commit: "text1|" + // after commit: "text1text2|" + assertThat(mBaseInputConnection.commitText("text2", "text1".length())).isTrue(); + verifyContent("text1text2", 10, 10, -1, -1); + + // before commit: "text1text2|" + // after commit: "text1text2text3|" + assertThat(mBaseInputConnection.commitText("text3", 100)).isTrue(); + verifyContent("text1text2text3", 15, 15, -1, -1); + + // before commit: "text1text2text3|" + // after commit: "text1text2text3text4|" + // BUG(b/21476564): this behavior is inconsistent with API description. + assertThat(mBaseInputConnection.commitText("text4", 0)).isTrue(); + verifyContent("text1text2text3text4", 20, 20, -1, -1); + + // before commit: "text1text2text3text4|" + // after commit: "text1text2text3text|4text5" + assertThat(mBaseInputConnection.commitText("text5", -1)).isTrue(); + verifyContent("text1text2text3text4text5", 19, 19, -1, -1); + + // before commit: "text1text2text3text|4text5" + // after commit: "text1text2text3te|xttext64text5" + assertThat(mBaseInputConnection.commitText("text6", -2)).isTrue(); + verifyContent("text1text2text3texttext64text5", 17, 17, -1, -1); + + // before commit: "text1text2text3te|xttext64text5" + // after commit: "|text1text2text3tetext7xttext64text5" + assertThat(mBaseInputConnection.commitText("text7", -100)).isTrue(); + verifyContent("text1text2text3tetext7xttext64text5", 0, 0, -1, -1); + } + + @Test + public void testCommitText_toEditorWithSelection() { + // before commit: "123|456|789" + // before commit: "123text|789" + prepareContent("123456789", 3, 6, -1, -1); + assertThat(mBaseInputConnection.commitText("text", 1)).isTrue(); + verifyContent("123text789", 7, 7, -1, -1); + + // before commit: "|123|" + // before commit: "|text" + prepareContent("123", 0, 3, -1, -1); + assertThat(mBaseInputConnection.commitText("text", 0)).isTrue(); + verifyContent("text", 0, 0, -1, -1); + } + + @Test + public void testCommitText_toEditorWithComposing() { + // before commit: "123456|789" + // --- + // before commit: "123text|789" + prepareContent("123456789", 6, 6, 3, 6); + assertThat(mBaseInputConnection.commitText("text", 1)).isTrue(); + verifyContent("123text789", 7, 7, -1, -1); + + // before commit: "123456789|" + // --- + // before commit: "123text|789" + prepareContent("123456789", 6, 6, 3, 6); + assertThat(mBaseInputConnection.commitText("text", 1)).isTrue(); + verifyContent("123text789", 7, 7, -1, -1); + + // before commit: "|123456789|" + // --- + // before commit: "123text|789" + prepareContent("123456789", 0, 9, 3, 6); + assertThat(mBaseInputConnection.commitText("text", 1)).isTrue(); + verifyContent("123text789", 7, 7, -1, -1); + } + + @Test + public void deleteSurroundingText_fromEditorWithoutSelectionAndComposing() { + // before delete: "123456789|" + // after delete: "123456|" + prepareContent("123456789", 9, 9, -1, -1); + assertThat(mBaseInputConnection.deleteSurroundingText(3, 0)).isTrue(); + verifyContent("123456", 6, 6, -1, -1); + + // before delete: "123456|" + // after delete: "|" + assertThat(mBaseInputConnection.deleteSurroundingText(100, 0)).isTrue(); + verifyContent("", 0, 0, -1, -1); + + // before commit: "|123456789" + // after delete: "|456789" + prepareContent("123456789", 0, 0, -1, -1); + assertThat(mBaseInputConnection.deleteSurroundingText(0, 3)).isTrue(); + verifyContent("456789", 0, 0, -1, -1); + + // before delete: "|123456789" + // after delete: "|" + assertThat(mBaseInputConnection.deleteSurroundingText(0, 100)).isTrue(); + verifyContent("", 0, 0, -1, -1); + + // before delete: "123|456789" + // after delete: "1|789" + prepareContent("123456789", 3, 3, -1, -1); + assertThat(mBaseInputConnection.deleteSurroundingText(2, 3)).isTrue(); + verifyContent("1789", 1, 1, -1, -1); + } + + @Test + public void deleteSurroundingText_fromEditorSelectionOrComposing() { + // before delete: "123|456|789" + // before delete: "12|456|9" + prepareContent("123456789", 3, 6, -1, -1); + assertThat(mBaseInputConnection.deleteSurroundingText(1, 2)).isTrue(); + verifyContent("124569", 2, 5, -1, -1); + + // before delete: "12|456|9" + // before delete: "|456|" + assertThat(mBaseInputConnection.deleteSurroundingText(100, 100)).isTrue(); + verifyContent("456", 0, 3, -1, -1); + + // before commit: "123456|789" + // --- + // before commit: "1[456]|89" + prepareContent("123456789", 6, 6, 3, 6); + assertThat(mBaseInputConnection.deleteSurroundingText(2, 1)).isTrue(); + verifyContent("145689", 4, 4, 1, 4); + + // before commit: "1234|56789" + // - -- + // before commit: "124|56|89" + // - -- + prepareContent("123456789", 4, 4, 3, 6); + assertThat(mBaseInputConnection.deleteSurroundingText(1, 1)).isTrue(); + verifyContent("1245689", 3, 3, 2, 5); + } + + @Test + public void deleteSurroundingText_negativeLength_willBeIgnored() { + // before delete: "123|45678" + // after delete: "123|45678" + prepareContent("123456789", 3, 3, -1, -1); + assertThat(mBaseInputConnection.deleteSurroundingText(-1, -1)).isTrue(); + verifyContent("123456789", 3, 3, -1, -1); + + // before delete: "123|45678" + // after delete: "123|5678" + assertThat(mBaseInputConnection.deleteSurroundingText(-1, 1)).isTrue(); + verifyContent("12356789", 3, 3, -1, -1); + + // before delete: "123|45678" + // after delete: "12|45678" + prepareContent("123456789", 3, 3, -1, -1); + assertThat(mBaseInputConnection.deleteSurroundingText(1, -1)).isTrue(); + verifyContent("12456789", 2, 2, -1, -1); + } + + @Test + public void testFinishComposingText() { + // before finish composing: "123456|789" + // --- + // before finish composing: "123456|789" + prepareContent("123456789", 6, 6, 3, 6); + assertThat(mBaseInputConnection.finishComposingText()).isTrue(); + verifyContent("123456789", 6, 6, -1, -1); + + // before finish composing: "123456789|" + // --- + // before finish composing: "123456789|" + prepareContent("123456789", 9, 9, 3, 6); + assertThat(mBaseInputConnection.finishComposingText()).isTrue(); + verifyContent("123456789", 9, 9, -1, -1); + + // before finish composing: "|123456789|" + // --- + // before finish composing: "|123456789|" + prepareContent("123456789", 0, 9, 3, 6); + assertThat(mBaseInputConnection.finishComposingText()).isTrue(); + verifyContent("123456789", 0, 9, -1, -1); + + // before finish composing: "1234|5|6789|" + // ---- - ---- + // before finish composing: "1234|5|6789" + prepareContent("123456789", 4, 5, 0, 9); + assertThat(mBaseInputConnection.finishComposingText()).isTrue(); + verifyContent("123456789", 4, 5, -1, -1); + } + + @Test + public void testGetCursorCapsMode() { + // "|" + prepareContent("", 0, 0, -1, -1); + verifyCursorCapsModeWithMode("", 0); + + // Hello| + prepareContent("Hello", 5, 5, -1, -1); + verifyCursorCapsModeWithMode("Hello", 5); + + // Hello. | + prepareContent("Hello. ", 7, 7, -1, -1); + verifyCursorCapsModeWithMode("Hello. ", 7); + + // Hello. |Hi| + prepareContent("Hello. Hi", 7, 9, -1, -1); + verifyCursorCapsModeWithMode("Hello. Hi", 7); + + // Hello. | + // ----- + prepareContent("Hello. ", 7, 7, 0, 5); + verifyCursorCapsModeWithMode("Hello. ", 7); + } + + private void verifyCursorCapsModeWithMode(CharSequence text, int off) { + for (int reqMode : CURSOR_CAPS_MODES) { + assertThat(mBaseInputConnection.getCursorCapsMode(reqMode)) + .isEqualTo(TextUtils.getCapsMode(text, off, reqMode)); + } + } + + @Test + public void testSetComposingText_toEditorWithoutSelectionAndComposing() { + // before set composing text: "|" + // after set composing text: "abc|" + // --- + assertThat(mBaseInputConnection.setComposingText("abc", 1)).isTrue(); + verifyContent("abc", 3, 3, 0, 3); + + // before set composing text: "abc|" + // after set composing text: "abcdef|" + // --- + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingText("def", 100)).isTrue(); + verifyContent("abcdef", 6, 6, 3, 6); + + // before set composing text: "abc|" + // after set composing text: "abcdef|" + // --- + // BUG(b/21476564): this behavior is inconsistent with API description. + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingText("def", 0)).isTrue(); + verifyContent("abcdef", 6, 6, 3, 6); + + // before set composing text: "abc|" + // after set composing text: "ab|cdef" + // --- + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingText("def", -1)).isTrue(); + verifyContent("abcdef", 2, 2, 3, 6); + + // before set composing text: "abc|" + // after set composing text: "|abcdef" + // --- + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingText("def", -100)).isTrue(); + verifyContent("abcdef", 0, 0, 3, 6); + } + + @Test + public void testSetComposingText_toEditorWithComposing() { + // before set composing text: "abc|" + // --- + // after set composing text: "def|" + // --- + prepareContent("abc", 3, 3, 0, 3); + assertThat(mBaseInputConnection.setComposingText("def", 1)).isTrue(); + verifyContent("def", 3, 3, 0, 3); + + // before set composing text: "abc|" + // --- + // after set composing text: "hijkl|" + // ----- + assertThat(mBaseInputConnection.setComposingText("hijkl", 1)).isTrue(); + verifyContent("hijkl", 5, 5, 0, 5); + + // before set composing text: "hijkl|" + // ----- + // after set composing text: "|mn" + // -- + assertThat(mBaseInputConnection.setComposingText("mn", 0)).isTrue(); + verifyContent("mn", 0, 0, 0, 2); + + // before set composing text: "|mn" + // -- + // after set composing text: "|opq" + // --- + assertThat(mBaseInputConnection.setComposingText("opq", -1)).isTrue(); + verifyContent("opq", 0, 0, 0, 3); + } + + @Test + public void testSetComposingText_toEditorWithSelection() { + // before set composing text: "|abc|" + // after set composing text: "defgh|" + // ----- + prepareContent("abc", 0, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingText("defgh", 1)).isTrue(); + verifyContent("defgh", 5, 5, 0, 5); + + // before set composing text: "a|bcdef|g" + // after set composing text: "a|123g" + // --- + prepareContent("abcdefg", 1, 6, -1, -1); + assertThat(mBaseInputConnection.setComposingText("123", 0)).isTrue(); + verifyContent("a123g", 1, 1, 1, 4); + + // before set composing text: "a|bcdef|g" + // --- + // after set composing text: "ab123456|fg" + // ------ + prepareContent("abcdefg", 1, 6, 2, 5); + assertThat(mBaseInputConnection.setComposingText("123456", 1)).isTrue(); + verifyContent("ab123456fg", 8, 8, 2, 8); + + // before set composing text: "a|bc" + // ---- + // after set composing text: "|12345" + // ----- + prepareContent("abc", 1, 1, 0, 3); + assertThat(mBaseInputConnection.setComposingText("12345", -1)).isTrue(); + verifyContent("12345", 0, 0, 0, 5); + } + + @Test + public void testSetComposingRegion_toEditorWithoutSelectionAndComposing() { + // before set composing region: "|" + // after set composing region: "|" + assertThat(mBaseInputConnection.setComposingRegion(1, 1)).isTrue(); + verifyContent("", 0, 0, -1, -1); + + // before set composing region: "abc|" + // after set composing region: "abc|" + // --- + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(0, 3)).isTrue(); + verifyContent("abc", 3, 3, 0, 3); + + // before set composing region: "abc|" + // after set composing region: "abc|" + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(1, 1)).isTrue(); + verifyContent("abc", 3, 3, -1, -1); + + // before set composing region: "abc|" + // after set composing region: "abc|" + // - + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(1, 2)).isTrue(); + verifyContent("abc", 3, 3, 1, 2); + + // before set composing region: "abc|" + // after set composing region: "abc|" + // --- + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(3, 0)).isTrue(); + verifyContent("abc", 3, 3, 0, 3); + + // before set composing region: "abc|" + // after set composing region: "abc|" + // --- + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(-100, 100)).isTrue(); + verifyContent("abc", 3, 3, 0, 3); + } + + @Test + public void testSetComposingRegion_toEditorWithSelection() { + // before set composing region: "|abc|" + // after set composing region: "|abc|" + // --- + prepareContent("abc", 0, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(0, 3)).isTrue(); + verifyContent("abc", 0, 3, 0, 3); + + // before set composing region: "ab|cd|ef" + // after set composing region: "ab|cd|ef" + // - -- - + prepareContent("abcdef", 2, 4, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(1, 5)).isTrue(); + verifyContent("abcdef", 2, 4, 1, 5); + } + + @Test + public void testSetComposingRegion_toEditorWithComposing() { + // before set composing region: "abc|" + // --- + // after set composing region: "abc|" + // - + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setComposingRegion(1, 2)).isTrue(); + verifyContent("abc", 3, 3, 1, 2); + + // before set composing region: "ab|cd|ef" + // -- + // after set composing region: "ab|cd|ef" + // - -- - + prepareContent("abcdef", 2, 4, 2, 4); + assertThat(mBaseInputConnection.setComposingRegion(1, 5)).isTrue(); + verifyContent("abcdef", 2, 4, 1, 5); + } + + @Test + public void testSetSelection_toEditorWithoutComposing() { + // before set selection: "|" + // after set selection: "|" + assertThat(mBaseInputConnection.setSelection(0, 0)).isTrue(); + assertThat(mBaseInputConnection.setSelection(1, 1)).isTrue(); + assertThat(mBaseInputConnection.setSelection(-1, -1)).isTrue(); + + // before set selection: "abc|" + // after set selection: "a|b|c" + prepareContent("abc", 3, 3, -1, -1); + assertThat(mBaseInputConnection.setSelection(1, 1)).isTrue(); + verifyContent("abc", 1, 1, -1, -1); + + // before set selection: "abcdef|" + // after set selection: "ab|cd|ef" + prepareContent("abcdef", 6, 6, -1, -1); + assertThat(mBaseInputConnection.setSelection(4, 2)).isTrue(); + verifyContent("abcdef", 4, 2, -1, -1); + + // before set selection: "|abc" + // after set selection: "|abc" + prepareContent("abc", 0, 0, -1, -1); + assertThat(mBaseInputConnection.setSelection(0, 100)).isTrue(); + verifyContent("abc", 0, 0, -1, -1); + + // before set selection: "|abc" + // after set selection: "ab|c" + prepareContent("abc", 0, 0, -1, -1); + assertThat(mBaseInputConnection.setSelection(2, 2)).isTrue(); + verifyContent("abc", 2, 2, -1, -1); + + // before set selection: "|abc" + // after set selection: "|abc" + prepareContent("abc", 0, 0, -1, -1); + assertThat(mBaseInputConnection.setSelection(-1, 2)).isTrue(); + verifyContent("abc", 0, 0, -1, -1); + } + + @Test + public void testSetSelection_toEditorWithComposing() { + // before set selection: "abc|" + // --- + // after set selection: "a|bc" + // - -- + prepareContent("abc", 3, 3, 0, 3); + assertThat(mBaseInputConnection.setSelection(1, 1)).isTrue(); + verifyContent("abc", 1, 1, 0, 3); + + // before set selection: "abcdef|" + // --- + // after set selection: "|abcdef|" + // --- + prepareContent("abcdef", 6, 6, 2, 5); + assertThat(mBaseInputConnection.setSelection(0, 6)).isTrue(); + verifyContent("abcdef", 0, 6, 2, 5); + } + + @Test + public void testGetText_noStyle() { + // "123|456|789" + prepareContent("123456789", 3, 6, -1, -1); + + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(1, 0), "3"); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(1, 0), "7"); + verifyContentEquals(mBaseInputConnection.getSelectedText(0), "456"); + // This falls back to default implementation in {@code InputConnection}, which always return + // -1 for offset. + assertThat( + mBaseInputConnection + .getSurroundingText(1, 1, 0) + .isEqualTo(new SurroundingText("34567", 1, 4, -1))) + .isTrue(); + + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(100, 0), "123"); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(100, 0), "789"); + assertThat( + mBaseInputConnection + .getSurroundingText(100, 100, 0) + .isEqualTo(new SurroundingText("123456789", 3, 6, -1))) + .isTrue(); + + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(0, 0), ""); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(0, 0), ""); + assertThat( + mBaseInputConnection + .getSurroundingText(0, 0, 0) + .isEqualTo(new SurroundingText("456", 0, 3, -1))) + .isTrue(); + + int cursorCapsMode = + TextUtils.getCapsMode( + "123456789", + 3, + TextUtils.CAP_MODE_CHARACTERS + | TextUtils.CAP_MODE_WORDS + | TextUtils.CAP_MODE_SENTENCES); + TextSnapshot expectedTextSnapshot = + new TextSnapshot( + new SurroundingText("123456789", 3, 6, -1), -1, -1, cursorCapsMode); + verifyTextSnapshotContentEquals(mBaseInputConnection.takeSnapshot(), expectedTextSnapshot); + } + + @Test + public void testGetText_withStyle() { + // "123|456|789" + SpannableStringBuilder text = new SpannableStringBuilder("123456789"); + SuggestionSpan suggestionSpanA = + new SuggestionSpan(Locale.US, new String[] {"a"}, SuggestionSpan.FLAG_EASY_CORRECT); + SuggestionSpan suggestionSpanB = + new SuggestionSpan(Locale.US, new String[] {"b"}, SuggestionSpan.FLAG_EASY_CORRECT); + SuggestionSpan suggestionSpanC = + new SuggestionSpan(Locale.US, new String[] {"c"}, SuggestionSpan.FLAG_EASY_CORRECT); + text.setSpan(suggestionSpanA, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(suggestionSpanB, 3, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(suggestionSpanC, 6, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + prepareContent(text, 3, 6, -1, -1); + + verifySpannableString( + mBaseInputConnection.getTextBeforeCursor(1, InputConnection.GET_TEXT_WITH_STYLES), + "3", + 1, + new int[][] {new int[] {0, 1}}, + new Object[] {suggestionSpanA}); + verifySpannableString( + mBaseInputConnection.getTextAfterCursor(1, InputConnection.GET_TEXT_WITH_STYLES), + "7", + 1, + new int[][] {new int[] {0, 1}}, + new Object[] {suggestionSpanC}); + verifySpannableString( + mBaseInputConnection.getSelectedText(InputConnection.GET_TEXT_WITH_STYLES), + "456", + 1, + new int[][] {new int[] {0, 3}}, + new Object[] {suggestionSpanB}); + CharSequence surroundTextString = + TextUtils.concat( + text.subSequence(0, 3), text.subSequence(3, 6), text.subSequence(6, 9)); + assertThat( + mBaseInputConnection + .getSurroundingText(100, 100, InputConnection.GET_TEXT_WITH_STYLES) + .isEqualTo(new SurroundingText(surroundTextString, 3, 6, -1))) + .isTrue(); + + int cursorCapsMode = + TextUtils.getCapsMode( + "123456789", + 3, + TextUtils.CAP_MODE_CHARACTERS + | TextUtils.CAP_MODE_WORDS + | TextUtils.CAP_MODE_SENTENCES); + TextSnapshot expectedTextSnapshot = + new TextSnapshot( + new SurroundingText(surroundTextString, 3, 6, -1), -1, -1, cursorCapsMode); + verifyTextSnapshotContentEquals(mBaseInputConnection.takeSnapshot(), expectedTextSnapshot); + } + + private void prepareContent( + CharSequence text, + int selectionStart, + int selectionEnd, + int composingSpanStart, + int composingSpanEnd) { + mEditable.clear(); + mEditable.append(text); + Selection.setSelection(mEditable, selectionStart, selectionEnd); + if (isValidComposingSpan(text.length(), composingSpanStart, composingSpanEnd)) { + BaseInputConnection.setComposingSpans(mEditable, composingSpanStart, composingSpanEnd); + } + verifyContent(text, selectionStart, selectionEnd, composingSpanStart, composingSpanEnd); + } + + private boolean isValidComposingSpan( + int textLength, int composingSpanStart, int composingSpanEnd) { + return composingSpanStart >= 0 + && composingSpanStart <= textLength + && composingSpanEnd >= 0 + && composingSpanEnd <= textLength; + } + + private void verifyContent( + CharSequence text, + int selectionStart, + int selectionEnd, + int composingSpanStart, + int composingSpanEnd) { + assertThat(mEditable).isNotNull(); + verifyContentEquals(mEditable, text.toString()); + assertThat(Selection.getSelectionStart(mEditable)).isEqualTo(selectionStart); + assertThat(Selection.getSelectionEnd(mEditable)).isEqualTo(selectionEnd); + assertThat(BaseInputConnection.getComposingSpanStart(mEditable)) + .isEqualTo(composingSpanStart); + assertThat(BaseInputConnection.getComposingSpanEnd(mEditable)).isEqualTo(composingSpanEnd); + } + + private void verifySpannableString( + CharSequence text, + String expectedString, + int expectedSpanSize, + int[][] expectedSpanRanges, + Object[] expectedSpans) { + verifyContentEquals(text, expectedString); + SpannableStringBuilder spannableString = new SpannableStringBuilder(text); + Object[] spanList = spannableString.getSpans(0, text.length(), Object.class); + assertThat(spanList).isNotNull(); + assertThat(spanList).hasLength(expectedSpanSize); + for (int i = 0; i < expectedSpanSize; i++) { + assertThat(spannableString.getSpanStart(spanList[i])) + .isEqualTo(expectedSpanRanges[i][0]); + assertThat(spannableString.getSpanEnd(spanList[i])).isEqualTo(expectedSpanRanges[i][1]); + } + for (int i = 0; i < expectedSpanSize; i++) { + assertThat(spanList[i]).isEqualTo(expectedSpans[i]); + } + } + + private void verifyContentEquals(CharSequence text, String expectedText) { + assertThat(text.toString().contentEquals(expectedText)).isTrue(); + } + + private void verifyTextSnapshotContentEquals(TextSnapshot t1, TextSnapshot t2) { + assertThat(t1.getCompositionStart()).isEqualTo(t2.getCompositionStart()); + assertThat(t1.getCompositionEnd()).isEqualTo(t2.getCompositionEnd()); + assertThat(t1.getCursorCapsMode()).isEqualTo(t2.getCursorCapsMode()); + assertThat(t1.getSurroundingText().isEqualTo(t2.getSurroundingText())).isTrue(); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java index 50ce335cbec6..047f33074460 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java @@ -16,18 +16,21 @@ package android.view.inputmethod; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; +import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.SuggestionSpan; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Locale; + @SmallTest @RunWith(AndroidJUnit4.class) public class SurroundingTextTest { @@ -35,22 +38,22 @@ public class SurroundingTextTest { @Test public void testSurroundingTextBasicCreation() { SurroundingText surroundingText1 = new SurroundingText("test", 0, 0, 0); - assertThat(surroundingText1.getText(), is("test")); - assertThat(surroundingText1.getSelectionStart(), is(0)); - assertThat(surroundingText1.getSelectionEnd(), is(0)); - assertThat(surroundingText1.getOffset(), is(0)); + assertThat(surroundingText1.getText().toString()).isEqualTo("test"); + assertThat(surroundingText1.getSelectionStart()).isEqualTo(0); + assertThat(surroundingText1.getSelectionEnd()).isEqualTo(0); + assertThat(surroundingText1.getOffset()).isEqualTo(0); SurroundingText surroundingText2 = new SurroundingText("", -1, -1, -1); - assertThat(surroundingText2.getText(), is("")); - assertThat(surroundingText2.getSelectionStart(), is(-1)); - assertThat(surroundingText2.getSelectionEnd(), is(-1)); - assertThat(surroundingText2.getOffset(), is(-1)); + assertThat(surroundingText2.getText().toString()).isEmpty(); + assertThat(surroundingText2.getSelectionStart()).isEqualTo(-1); + assertThat(surroundingText2.getSelectionEnd()).isEqualTo(-1); + assertThat(surroundingText2.getOffset()).isEqualTo(-1); SurroundingText surroundingText3 = new SurroundingText("hello", 0, 5, 0); - assertThat(surroundingText3.getText(), is("hello")); - assertThat(surroundingText3.getSelectionStart(), is(0)); - assertThat(surroundingText3.getSelectionEnd(), is(5)); - assertThat(surroundingText3.getOffset(), is(0)); + assertThat(surroundingText3.getText().toString()).isEqualTo("hello"); + assertThat(surroundingText3.getSelectionStart()).isEqualTo(0); + assertThat(surroundingText3.getSelectionEnd()).isEqualTo(5); + assertThat(surroundingText3.getOffset()).isEqualTo(0); } @Test @@ -62,20 +65,73 @@ public class SurroundingTextTest { parcel.setDataPosition(0); SurroundingText surroundingTextFromParcel = SurroundingText.CREATOR.createFromParcel(parcel); - assertThat(surroundingText.getText(), is("text")); - assertThat(surroundingText.getSelectionStart(), is(0)); - assertThat(surroundingText.getSelectionEnd(), is(1)); - assertThat(surroundingText.getOffset(), is(2)); - assertThat(surroundingTextFromParcel.getText(), is("text")); - assertThat(surroundingTextFromParcel.getSelectionStart(), is(0)); - assertThat(surroundingTextFromParcel.getSelectionEnd(), is(1)); - assertThat(surroundingTextFromParcel.getOffset(), is(2)); + assertThat(surroundingText.getText().toString()).isEqualTo("text"); + assertThat(surroundingText.getSelectionStart()).isEqualTo(0); + assertThat(surroundingText.getSelectionEnd()).isEqualTo(1); + assertThat(surroundingText.getOffset()).isEqualTo(2); + assertThat(surroundingTextFromParcel.getText().toString()).isEqualTo("text"); + assertThat(surroundingTextFromParcel.getSelectionStart()).isEqualTo(0); + assertThat(surroundingTextFromParcel.getSelectionEnd()).isEqualTo(1); + assertThat(surroundingTextFromParcel.getOffset()).isEqualTo(2); } @Test - public void testIsEqualComparesText() { - final SurroundingText text1 = new SurroundingText("hello", 0, 1, 0); - final SurroundingText text2 = new SurroundingText("there", 0, 1, 0); - assertFalse(text1.isEqualTo(text2)); + public void testIsEqualComparesText_isNotEqualTo() { + final SurroundingText text = new SurroundingText("hello", 0, 1, 0); + + verifySurroundingTextNotEquals(text, new SurroundingText("there", 0, 1, 0)); + verifySurroundingTextNotEquals(text, new SurroundingText("hello", 0, 1, -1)); + verifySurroundingTextNotEquals(text, new SurroundingText("hello", 0, 0, 0)); + verifySurroundingTextNotEquals(text, new SurroundingText("hello", 1, 1, 0)); + + SpannableString spannableString = new SpannableString("hello"); + spannableString.setSpan( + new SuggestionSpan( + Locale.US, new String[] {"Hello"}, SuggestionSpan.FLAG_EASY_CORRECT), + 0, + 5, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + verifySurroundingTextNotEquals(text, new SurroundingText(spannableString, 1, 1, 0)); + } + + private void verifySurroundingTextNotEquals(SurroundingText text1, SurroundingText text2) { + assertThat(text1.isEqualTo(text2)).isFalse(); + assertThat(text1).isNotEqualTo(text2); + assertThat(text1.equals(text2)).isFalse(); + assertThat(text1.hashCode()).isNotEqualTo(text2.hashCode()); + assertThat(text1 == text2).isFalse(); + } + + @Test + public void testIsEqualComparesText_isEqualTo() { + final SurroundingText text = new SurroundingText("hello", 0, 1, 0); + + verifySurroundingTextEquals( + text, + new SurroundingText("hello", 0, 1, 0), + /*equals=*/ false, + /*isSameInstance=*/ false); + verifySurroundingTextEquals(text, text, /*equals=*/ true, /*isSameInstance=*/ true); + } + + private void verifySurroundingTextEquals( + SurroundingText text1, SurroundingText text2, boolean equals, boolean isSameInstance) { + assertThat(text1.isEqualTo(text2)).isTrue(); + if (equals) { + assertThat(text1).isEqualTo(text2); + assertThat(text1.equals(text2)).isTrue(); + assertThat(text1.hashCode()).isEqualTo(text2.hashCode()); + } else { + assertThat(text1).isNotEqualTo(text2); + assertThat(text1.equals(text2)).isFalse(); + assertThat(text1.hashCode()).isNotEqualTo(text2.hashCode()); + } + if (isSameInstance) { + assertThat(text1 == text2).isTrue(); + assertThat(text1).isSameInstanceAs(text2); + } else { + assertThat(text1 == text2).isFalse(); + assertThat(text1).isNotSameInstanceAs(text2); + } } } diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java index a22dd1c63ddf..516dee7dc9aa 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -161,4 +161,33 @@ public class LongArrayMultiStateCounterTest { assertThrows(RuntimeException.class, () -> LongArrayMultiStateCounter.CREATOR.createFromParcel(parcel)); } + + @Test + public void combineValues() { + long[] values = new long[] {0, 1, 2, 3, 42}; + LongArrayMultiStateCounter.LongArrayContainer container = + new LongArrayMultiStateCounter.LongArrayContainer(values.length); + container.setValues(values); + + long[] out = new long[3]; + int[] indexes = {2, 1, 1, 0, 0}; + boolean nonZero = container.combineValues(out, indexes); + assertThat(nonZero).isTrue(); + assertThat(out).isEqualTo(new long[]{45, 3, 0}); + + // All zeros + container.setValues(new long[]{0, 0, 0, 0, 0}); + nonZero = container.combineValues(out, indexes); + assertThat(nonZero).isFalse(); + assertThat(out).isEqualTo(new long[]{0, 0, 0}); + + // Index out of range + IndexOutOfBoundsException e1 = assertThrows( + IndexOutOfBoundsException.class, + () -> container.combineValues(out, new int[]{0, 1, -1, 0, 0})); + assertThat(e1.getMessage()).isEqualTo("Index -1 is out of bounds: [0, 2]"); + IndexOutOfBoundsException e2 = assertThrows(IndexOutOfBoundsException.class, + () -> container.combineValues(out, new int[]{0, 1, 4, 0, 0})); + assertThat(e2.getMessage()).isEqualTo("Index 4 is out of bounds: [0, 2]"); + } } diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index a186aca4648f..af96c743f400 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -302,6 +302,11 @@ key 317 BUTTON_THUMBL key 318 BUTTON_THUMBR +key 329 STYLUS_BUTTON_TERTIARY +key 331 STYLUS_BUTTON_PRIMARY +key 332 STYLUS_BUTTON_SECONDARY + + # key 352 "KEY_OK" key 353 DPAD_CENTER # key 354 "KEY_GOTO" @@ -424,6 +429,8 @@ key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE key usage 0x0c0173 MEDIA_AUDIO_TRACK key usage 0x0c019C PROFILE_SWITCH key usage 0x0c01A2 ALL_APPS +key usage 0x0d0044 STYLUS_BUTTON_PRIMARY +key usage 0x0d005a STYLUS_BUTTON_SECONDARY # Joystick and game controller axes. # Axes that are not mapped will be assigned generic axis numbers by the input subsystem. diff --git a/docs/html/reference/images/input_method/stylus_handwriting/delete_range_gesture_rects.png b/docs/html/reference/images/input_method/stylus_handwriting/delete_range_gesture_rects.png Binary files differnew file mode 100644 index 000000000000..185582017ed6 --- /dev/null +++ b/docs/html/reference/images/input_method/stylus_handwriting/delete_range_gesture_rects.png diff --git a/docs/html/reference/images/input_method/stylus_handwriting/select_range_gesture_rects.png b/docs/html/reference/images/input_method/stylus_handwriting/select_range_gesture_rects.png Binary files differnew file mode 100644 index 000000000000..e0801267fe6d --- /dev/null +++ b/docs/html/reference/images/input_method/stylus_handwriting/select_range_gesture_rects.png diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index a8ab6d9e494e..54d64280c6f7 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -670,8 +670,9 @@ public abstract class BaseCanvas { /** * @hide */ - public void punchHole(float left, float top, float right, float bottom, float rx, float ry) { - nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry); + public void punchHole(float left, float top, float right, float bottom, float rx, float ry, + float alpha) { + nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, alpha); } /** @@ -823,5 +824,5 @@ public abstract class BaseCanvas { float hOffset, float vOffset, int flags, long nativePaint); private static native void nPunchHole(long renderer, float left, float top, float right, - float bottom, float rx, float ry); + float bottom, float rx, float ry, float alpha); } diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index d06f665631cc..1ba79b87e87c 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -610,8 +610,9 @@ public class BaseRecordingCanvas extends Canvas { * @hide */ @Override - public void punchHole(float left, float top, float right, float bottom, float rx, float ry) { - nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry); + public void punchHole(float left, float top, float right, float bottom, float rx, float ry, + float alpha) { + nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, alpha); } @FastNative @@ -742,5 +743,5 @@ public class BaseRecordingCanvas extends Canvas { @FastNative private static native void nPunchHole(long renderer, float left, float top, float right, - float bottom, float rx, float ry); + float bottom, float rx, float ry, float alpha); } 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 6af3d2bf4915..126f8350839c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -139,6 +139,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @Override + public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) { + // TODO: Implement this method + } + + @Override + public void clearSplitAttributesCalculator() { + // TODO: Implement this method + } + @NonNull List<EmbeddingRule> getSplitRules() { return mSplitRules; @@ -1516,13 +1526,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen .toActivityStack(); final ActivityStack secondaryContainer = container.getSecondaryContainer() .toActivityStack(); + final SplitAttributes.SplitType splitType = shouldShowSideBySide(container) + ? new SplitAttributes.SplitType.RatioSplitType( + container.getSplitRule().getSplitRatio()) + : new SplitAttributes.SplitType.ExpandContainersSplitType(); final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer, // Splits that are not showing side-by-side are reported as having 0 split // ratio, since by definition in the API the primary container occupies no // width of the split when covered by the secondary. - shouldShowSideBySide(container) - ? container.getSplitRule().getSplitRatio() - : 0.0f); + // TODO(b/241042437): use v2 APIs for splitAttributes + new SplitAttributes.Builder() + .setSplitType(splitType) + .setLayoutDirection(container.getSplitRule().getLayoutDirection()) + .build() + ); splitStates.add(splitState); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index cdee9e386b33..af5d8c561874 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -16,7 +16,6 @@ package androidx.window.extensions.embedding; -import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; @@ -41,30 +40,44 @@ class TaskFragmentAnimationAdapter { */ private static final int LAYER_NO_OVERRIDE = -1; + @NonNull final Animation mAnimation; + @NonNull final RemoteAnimationTarget mTarget; + @NonNull final SurfaceControl mLeash; + /** Area in absolute coordinate that the animation surface shouldn't go beyond. */ + @NonNull + private final Rect mWholeAnimationBounds = new Rect(); + @NonNull final Transformation mTransformation = new Transformation(); + @NonNull final float[] mMatrix = new float[9]; + @NonNull final float[] mVecs = new float[4]; + @NonNull final Rect mRect = new Rect(); private boolean mIsFirstFrame = true; private int mOverrideLayer = LAYER_NO_OVERRIDE; TaskFragmentAnimationAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { - this(animation, target, target.leash); + this(animation, target, target.leash, target.screenSpaceBounds); } /** * @param leash the surface to animate. + * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't + * go beyond. */ TaskFragmentAnimationAdapter(@NonNull Animation animation, - @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) { + @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash, + @NonNull Rect wholeAnimationBounds) { mAnimation = animation; mTarget = target; mLeash = leash; + mWholeAnimationBounds.set(wholeAnimationBounds); } /** @@ -94,23 +107,32 @@ class TaskFragmentAnimationAdapter { /** To be overridden by subclasses to adjust the animation surface change. */ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + // Update the surface position and alpha. mTransformation.getMatrix().postTranslate( mTarget.localBounds.left, mTarget.localBounds.top); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); - // Get current animation position. + + // Get current surface bounds in absolute coordinate. + // positionX/Y are in local coordinate, so minus the local offset to get the slide amount. final int positionX = Math.round(mMatrix[MTRANS_X]); final int positionY = Math.round(mMatrix[MTRANS_Y]); - // The exiting surface starts at position: mTarget.localBounds and moves with - // positionX varying. Offset our crop region by the amount we have slided so crop - // regions stays exactly on the original container in split. - final int cropOffsetX = mTarget.localBounds.left - positionX; - final int cropOffsetY = mTarget.localBounds.top - positionY; - final Rect cropRect = new Rect(); - cropRect.set(mTarget.localBounds); - // Because window crop uses absolute position. - cropRect.offsetTo(0, 0); - cropRect.offset(cropOffsetX, cropOffsetY); + final Rect cropRect = new Rect(mTarget.screenSpaceBounds); + final Rect localBounds = mTarget.localBounds; + cropRect.offset(positionX - localBounds.left, positionY - localBounds.top); + + // Store the current offset of the surface top left from (0,0) in absolute coordinate. + final int offsetX = cropRect.left; + final int offsetY = cropRect.top; + + // Intersect to make sure the animation happens within the whole animation bounds. + if (!cropRect.intersect(mWholeAnimationBounds)) { + // Hide the surface when it is outside of the animation area. + t.setAlpha(mLeash, 0); + } + + // cropRect is in absolute coordinate, so we need to translate it to surface top left. + cropRect.offset(-offsetX, -offsetY); t.setCrop(mLeash, cropRect); } @@ -124,52 +146,6 @@ class TaskFragmentAnimationAdapter { } /** - * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to - * animate together as one. This adapter will offset the animation leash to make the animate of - * two windows look like a single window. - */ - static class SplitAdapter extends TaskFragmentAnimationAdapter { - private final boolean mIsLeftHalf; - private final int mWholeAnimationWidth; - - /** - * @param isLeftHalf whether this is the left half of the animation. - * @param wholeAnimationWidth the whole animation windows width. - */ - SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target, - boolean isLeftHalf, int wholeAnimationWidth) { - super(animation, target); - mIsLeftHalf = isLeftHalf; - mWholeAnimationWidth = wholeAnimationWidth; - if (wholeAnimationWidth == 0) { - throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth"); - } - } - - @Override - void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { - float posX = mTarget.localBounds.left; - final float posY = mTarget.localBounds.top; - // This window is half of the whole animation window. Offset left/right to make it - // look as one with the other half. - mTransformation.getMatrix().getValues(mMatrix); - final int targetWidth = mTarget.localBounds.width(); - final float scaleX = mMatrix[MSCALE_X]; - final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2; - final float curOffset = targetWidth * (1 - scaleX) / 2; - final float offsetDiff = totalOffset - curOffset; - if (mIsLeftHalf) { - posX += offsetDiff; - } else { - posX -= offsetDiff; - } - mTransformation.getMatrix().postTranslate(posX, posY); - t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); - t.setAlpha(mLeash, mTransformation.getAlpha()); - } - } - - /** * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has * size change. */ @@ -177,7 +153,7 @@ class TaskFragmentAnimationAdapter { SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { // Start leash is the snapshot of the starting surface. - super(animation, target, target.startLeash); + super(animation, target, target.startLeash, target.screenSpaceBounds); } @Override diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 8af2d9c6810b..8c416e881059 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -213,10 +213,10 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { for (RemoteAnimationTarget target : targets) { if (target.mode != MODE_CLOSING) { openingTargets.add(target); - openingWholeScreenBounds.union(target.localBounds); + openingWholeScreenBounds.union(target.screenSpaceBounds); } else { closingTargets.add(target); - closingWholeScreenBounds.union(target.localBounds); + closingWholeScreenBounds.union(target.screenSpaceBounds); } } @@ -249,20 +249,8 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider, @NonNull Rect wholeAnimationBounds) { final Animation animation = animationProvider.apply(target, wholeAnimationBounds); - final Rect targetBounds = target.localBounds; - if (targetBounds.left == wholeAnimationBounds.left - && targetBounds.right != wholeAnimationBounds.right) { - // This is the left split of the whole animation window. - return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target, - true /* isLeftHalf */, wholeAnimationBounds.width()); - } else if (targetBounds.left != wholeAnimationBounds.left - && targetBounds.right == wholeAnimationBounds.right) { - // This is the right split of the whole animation window. - return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target, - false /* isLeftHalf */, wholeAnimationBounds.width()); - } - // Open/close window that fills the whole animation. - return new TaskFragmentAnimationAdapter(animation, target); + return new TaskFragmentAnimationAdapter(animation, target, target.leash, + wholeAnimationBounds); } @NonNull diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index 97d42391b6c4..ef5ea563de12 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -195,7 +195,10 @@ class TaskFragmentAnimationSpec { ? com.android.internal.R.anim.task_fragment_open_enter : com.android.internal.R.anim.task_fragment_open_exit); } - animation.initialize(target.localBounds.width(), target.localBounds.height(), + // Use the whole animation bounds instead of the change bounds, so that when multiple change + // targets are opening at the same time, the animation applied to each will be the same. + // Otherwise, we may see gap between the activities that are launching together. + animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(), wholeAnimationBounds.width(), wholeAnimationBounds.height()); animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); return animation; @@ -215,7 +218,10 @@ class TaskFragmentAnimationSpec { ? com.android.internal.R.anim.task_fragment_close_enter : com.android.internal.R.anim.task_fragment_close_exit); } - animation.initialize(target.localBounds.width(), target.localBounds.height(), + // Use the whole animation bounds instead of the change bounds, so that when multiple change + // targets are closing at the same time, the animation applied to each will be the same. + // Otherwise, we may see gap between the activities that are finishing together. + animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(), wholeAnimationBounds.width(), wholeAnimationBounds.height()); animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); return animation; diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex e9a1721fba2a..2c766d81d611 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 43679364b443..dec1e38914e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -44,6 +44,7 @@ import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; import android.window.ITaskOrganizerController; +import android.window.ScreenCapture; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; @@ -474,7 +475,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements * Take a screenshot of a task. */ public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { + Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) { final TaskAppearedInfo info = mTasks.get(taskInfo.taskId); if (info == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index a8764e05c3e2..d76ad3d27c70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -517,7 +517,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, getViewTreeObserver().removeOnComputeInternalInsetsListener(this); } - ActivityManager.RunningTaskInfo getTaskInfo() { + /** Returns the task info for the task in the TaskView. */ + @Nullable + public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index cc4db933ec9f..591e3476ecd9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -16,7 +16,6 @@ package com.android.wm.shell.activityembedding; -import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; @@ -42,31 +41,45 @@ class ActivityEmbeddingAnimationAdapter { */ private static final int LAYER_NO_OVERRIDE = -1; + @NonNull final Animation mAnimation; + @NonNull final TransitionInfo.Change mChange; + @NonNull final SurfaceControl mLeash; + /** Area in absolute coordinate that the animation surface shouldn't go beyond. */ + @NonNull + private final Rect mWholeAnimationBounds = new Rect(); + @NonNull final Transformation mTransformation = new Transformation(); + @NonNull final float[] mMatrix = new float[9]; + @NonNull final float[] mVecs = new float[4]; + @NonNull final Rect mRect = new Rect(); private boolean mIsFirstFrame = true; private int mOverrideLayer = LAYER_NO_OVERRIDE; ActivityEmbeddingAnimationAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change) { - this(animation, change, change.getLeash()); + this(animation, change, change.getLeash(), change.getEndAbsBounds()); } /** * @param leash the surface to animate, which is not necessary the same as - * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example. + * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example. + * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't + * go beyond. */ ActivityEmbeddingAnimationAdapter(@NonNull Animation animation, - @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) { + @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash, + @NonNull Rect wholeAnimationBounds) { mAnimation = animation; mChange = change; mLeash = leash; + mWholeAnimationBounds.set(wholeAnimationBounds); } /** @@ -96,23 +109,31 @@ class ActivityEmbeddingAnimationAdapter { /** To be overridden by subclasses to adjust the animation surface change. */ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + // Update the surface position and alpha. final Point offset = mChange.getEndRelOffset(); mTransformation.getMatrix().postTranslate(offset.x, offset.y); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); - // Get current animation position. + + // Get current surface bounds in absolute coordinate. + // positionX/Y are in local coordinate, so minus the local offset to get the slide amount. final int positionX = Math.round(mMatrix[MTRANS_X]); final int positionY = Math.round(mMatrix[MTRANS_Y]); - // The exiting surface starts at position: Change#getEndRelOffset() and moves with - // positionX varying. Offset our crop region by the amount we have slided so crop - // regions stays exactly on the original container in split. - final int cropOffsetX = offset.x - positionX; - final int cropOffsetY = offset.y - positionY; - final Rect cropRect = new Rect(); - cropRect.set(mChange.getEndAbsBounds()); - // Because window crop uses absolute position. - cropRect.offsetTo(0, 0); - cropRect.offset(cropOffsetX, cropOffsetY); + final Rect cropRect = new Rect(mChange.getEndAbsBounds()); + cropRect.offset(positionX - offset.x, positionY - offset.y); + + // Store the current offset of the surface top left from (0,0) in absolute coordinate. + final int offsetX = cropRect.left; + final int offsetY = cropRect.top; + + // Intersect to make sure the animation happens within the whole animation bounds. + if (!cropRect.intersect(mWholeAnimationBounds)) { + // Hide the surface when it is outside of the animation area. + t.setAlpha(mLeash, 0); + } + + // cropRect is in absolute coordinate, so we need to translate it to surface top left. + cropRect.offset(-offsetX, -offsetY); t.setCrop(mLeash, cropRect); } @@ -127,53 +148,6 @@ class ActivityEmbeddingAnimationAdapter { } /** - * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to - * animate together as one. This adapter will offset the animation leash to make the animate of - * two windows look like a single window. - */ - static class SplitAdapter extends ActivityEmbeddingAnimationAdapter { - private final boolean mIsLeftHalf; - private final int mWholeAnimationWidth; - - /** - * @param isLeftHalf whether this is the left half of the animation. - * @param wholeAnimationWidth the whole animation windows width. - */ - SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change, - boolean isLeftHalf, int wholeAnimationWidth) { - super(animation, change); - mIsLeftHalf = isLeftHalf; - mWholeAnimationWidth = wholeAnimationWidth; - if (wholeAnimationWidth == 0) { - throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth"); - } - } - - @Override - void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { - final Point offset = mChange.getEndRelOffset(); - float posX = offset.x; - final float posY = offset.y; - // This window is half of the whole animation window. Offset left/right to make it - // look as one with the other half. - mTransformation.getMatrix().getValues(mMatrix); - final int changeWidth = mChange.getEndAbsBounds().width(); - final float scaleX = mMatrix[MSCALE_X]; - final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2; - final float curOffset = changeWidth * (1 - scaleX) / 2; - final float offsetDiff = totalOffset - curOffset; - if (mIsLeftHalf) { - posX += offsetDiff; - } else { - posX -= offsetDiff; - } - mTransformation.getMatrix().postTranslate(posX, posY); - t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); - t.setAlpha(mLeash, mTransformation.getAlpha()); - } - } - - /** * Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has * size change. */ @@ -181,7 +155,7 @@ class ActivityEmbeddingAnimationAdapter { SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change, @NonNull SurfaceControl snapshotLeash) { - super(animation, change, snapshotLeash); + super(animation, change, snapshotLeash, change.getEndAbsBounds()); } @Override @@ -196,7 +170,9 @@ class ActivityEmbeddingAnimationAdapter { void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { super.onAnimationEnd(t); // Remove the screenshot leash after animation is finished. - t.remove(mLeash); + if (mLeash.isValid()) { + t.remove(mLeash); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 7e0795d11153..d88cc007c7b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -22,9 +22,9 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; -import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; +import android.util.ArraySet; import android.util.Log; import android.view.SurfaceControl; import android.view.animation.Animation; @@ -40,6 +40,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.BiFunction; /** To run the ActivityEmbedding animations. */ @@ -169,15 +170,12 @@ class ActivityEmbeddingAnimationRunner { final Rect openingWholeScreenBounds = new Rect(); final Rect closingWholeScreenBounds = new Rect(); for (TransitionInfo.Change change : info.getChanges()) { - final Rect bounds = new Rect(change.getEndAbsBounds()); - final Point offset = change.getEndRelOffset(); - bounds.offsetTo(offset.x, offset.y); if (Transitions.isOpeningType(change.getMode())) { openingChanges.add(change); - openingWholeScreenBounds.union(bounds); + openingWholeScreenBounds.union(change.getEndAbsBounds()); } else { closingChanges.add(change); - closingWholeScreenBounds.union(bounds); + closingWholeScreenBounds.union(change.getEndAbsBounds()); } } @@ -210,60 +208,73 @@ class ActivityEmbeddingAnimationRunner { @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider, @NonNull Rect wholeAnimationBounds) { final Animation animation = animationProvider.apply(change, wholeAnimationBounds); - final Rect bounds = new Rect(change.getEndAbsBounds()); - final Point offset = change.getEndRelOffset(); - bounds.offsetTo(offset.x, offset.y); - if (bounds.left == wholeAnimationBounds.left - && bounds.right != wholeAnimationBounds.right) { - // This is the left split of the whole animation window. - return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change, - true /* isLeftHalf */, wholeAnimationBounds.width()); - } else if (bounds.left != wholeAnimationBounds.left - && bounds.right == wholeAnimationBounds.right) { - // This is the right split of the whole animation window. - return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change, - false /* isLeftHalf */, wholeAnimationBounds.width()); - } - // Open/close window that fills the whole animation. - return new ActivityEmbeddingAnimationAdapter(animation, change); + return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(), + wholeAnimationBounds); } @NonNull private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); + final Set<TransitionInfo.Change> handledChanges = new ArraySet<>(); + + // For the first iteration, we prepare the animation for the change type windows. This is + // needed because there may be window that is reparented while resizing. In such case, we + // will do the following: + // 1. Capture a screenshot from the Activity surface. + // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface. + // 3. Animate the TaskFragment using Activity Change info (start/end bounds). + // This is because the TaskFragment surface/change won't contain the Activity's before its + // reparent. for (TransitionInfo.Change change : info.getChanges()) { - if (change.getMode() == TRANSIT_CHANGE - && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { - // This is the window with bounds change. - final WindowContainerToken parentToken = change.getParent(); - final Rect parentBounds; - if (parentToken != null) { - TransitionInfo.Change parentChange = info.getChange(parentToken); - parentBounds = parentChange != null - ? parentChange.getEndAbsBounds() - : change.getEndAbsBounds(); - } else { - parentBounds = change.getEndAbsBounds(); + if (change.getMode() != TRANSIT_CHANGE + || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { + continue; + } + + // This is the window with bounds change. + handledChanges.add(change); + final WindowContainerToken parentToken = change.getParent(); + TransitionInfo.Change boundsAnimationChange = change; + if (parentToken != null) { + // When the parent window is also included in the transition as an opening window, + // we would like to animate the parent window instead. + final TransitionInfo.Change parentChange = info.getChange(parentToken); + if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) { + // We won't create a separate animation for the parent, but to animate the + // parent for the child resizing. + handledChanges.add(parentChange); + boundsAnimationChange = parentChange; } - final Animation[] animations = - mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds); + } + + final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, + boundsAnimationChange.getEndAbsBounds()); + + // Create a screenshot based on change, but attach it to the top of the + // boundsAnimationChange. + final SurfaceControl screenshotLeash = getOrCreateScreenshot(change, + boundsAnimationChange, startTransaction); + if (screenshotLeash != null) { // Adapter for the starting screenshot leash. - final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction); - if (screenshotLeash != null) { - // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd - adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( - animations[0], change, screenshotLeash)); - } else { - Log.e(TAG, "Failed to take screenshot for change=" + change); - } - // Adapter for the ending bounds changed leash. - adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( - animations[1], change)); + // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd + adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( + animations[0], change, screenshotLeash)); + } else { + Log.e(TAG, "Failed to take screenshot for change=" + change); + } + // Adapter for the ending bounds changed leash. + adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( + animations[1], boundsAnimationChange)); + } + + // Handle the other windows that don't have bounds change in the same transition. + for (TransitionInfo.Change change : info.getChanges()) { + if (handledChanges.contains(change)) { + // Skip windows that we have already handled in the previous iteration. continue; } - // These are the other windows that don't have bounds change in the same transition. final Animation animation; if (!TransitionInfo.isIndependent(change, info)) { // No-op if it will be covered by the changing parent window. @@ -278,13 +289,27 @@ class ActivityEmbeddingAnimationRunner { return adapters; } - /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */ + /** + * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. + * The screenshot leash should be attached to the {@code animationChange} surface which we will + * animate later. + */ @Nullable - private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startTransaction) { - final Rect cropBounds = new Rect(change.getStartAbsBounds()); + private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange, + @NonNull TransitionInfo.Change animationChange, + @NonNull SurfaceControl.Transaction t) { + final SurfaceControl screenshotLeash = screenshotChange.getSnapshot(); + if (screenshotLeash != null) { + // If WM Core has already taken a screenshot, make sure it is reparented to the + // animation leash. + t.reparent(screenshotLeash, animationChange.getLeash()); + return screenshotLeash; + } + + // If WM Core hasn't taken a screenshot, take a screenshot now. + final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds()); cropBounds.offsetTo(0, 0); - return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds, - Integer.MAX_VALUE); + return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(), + animationChange.getLeash(), cropBounds, Integer.MAX_VALUE); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 6f06f28caff2..ad0dddf77002 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -185,8 +185,10 @@ class ActivityEmbeddingAnimationSpec { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter ? R.anim.task_fragment_open_enter : R.anim.task_fragment_open_exit); - final Rect bounds = change.getEndAbsBounds(); - animation.initialize(bounds.width(), bounds.height(), + // Use the whole animation bounds instead of the change bounds, so that when multiple change + // targets are opening at the same time, the animation applied to each will be the same. + // Otherwise, we may see gap between the activities that are launching together. + animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(), wholeAnimationBounds.width(), wholeAnimationBounds.height()); animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); return animation; @@ -203,8 +205,10 @@ class ActivityEmbeddingAnimationSpec { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter ? R.anim.task_fragment_close_enter : R.anim.task_fragment_close_exit); - final Rect bounds = change.getEndAbsBounds(); - animation.initialize(bounds.width(), bounds.height(), + // Use the whole animation bounds instead of the change bounds, so that when multiple change + // targets are closing at the same time, the animation applied to each will be the same. + // Otherwise, we may see gap between the activities that are finishing together. + animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(), wholeAnimationBounds.width(), wholeAnimationBounds.height()); animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); return animation; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index e0004fcaa060..521a65cc4df6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -16,7 +16,8 @@ package com.android.wm.shell.activityembedding; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static java.util.Objects.requireNonNull; @@ -84,12 +85,23 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - // TODO(b/207070762) Handle AE animation as a part of other transitions. - // Only handle the transition if all containers are embedded. + boolean containsEmbeddingSplit = false; for (TransitionInfo.Change change : info.getChanges()) { - if (!isEmbedded(change)) { + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + // Only animate the transition if all changes are in a Task with ActivityEmbedding. return false; } + if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) { + // Whether the Task contains any ActivityEmbedding split before or after the + // transition. + containsEmbeddingSplit = true; + } + } + if (!containsEmbeddingSplit) { + // Let the system to play the default animation if there is no ActivityEmbedding split + // window. This allows to play the app customized animation when there is no embedding, + // such as the device is in a folded state. + return false; } // Start ActivityEmbedding animation. @@ -119,8 +131,4 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle } callback.onTransitionFinished(null /* wct */, null /* wctCB */); } - - private static boolean isEmbedded(@NonNull TransitionInfo.Change change) { - return (change.getFlags() & FLAG_IS_EMBEDDED) != 0; - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index cfbe1b3caf7a..54c91dde7668 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -53,13 +53,13 @@ import android.util.IntProperty; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; -import android.view.SurfaceControl; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.window.ScreenCapture; import androidx.annotation.Nullable; @@ -508,7 +508,7 @@ public class BubbleExpandedView extends LinearLayout { /** Return a GraphicBuffer with the contents of the task view surface. */ @Nullable - SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() { + ScreenCapture.ScreenshotHardwareBuffer snapshotActivitySurface() { if (mIsOverflow) { // For now, just snapshot the view and return it as a hw buffer so that the animation // code for both the tasks and overflow can be the same @@ -517,7 +517,7 @@ public class BubbleExpandedView extends LinearLayout { p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight())); p.endRecording(); Bitmap snapshot = Bitmap.createBitmap(p); - return new SurfaceControl.ScreenshotHardwareBuffer( + return new ScreenCapture.ScreenshotHardwareBuffer( snapshot.getHardwareBuffer(), snapshot.getColorSpace(), false /* containsSecureLayers */, @@ -526,7 +526,7 @@ public class BubbleExpandedView extends LinearLayout { if (mTaskView == null || mTaskView.getSurfaceControl() == null) { return null; } - return SurfaceControl.captureLayers( + return ScreenCapture.captureLayers( mTaskView.getSurfaceControl(), new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()), 1 /* scale */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index aeaf6eda9809..2d9c2a9145ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -51,7 +51,6 @@ import android.util.Log; import android.view.Choreographer; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; @@ -64,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import android.window.ScreenCapture; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -244,7 +244,7 @@ public class BubbleStackView extends FrameLayout * Buffer containing a screenshot of the animating-out bubble. This is drawn into the * SurfaceView during animations. */ - private SurfaceControl.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer; + private ScreenCapture.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer; private BubbleFlyoutView mFlyout; /** Runnable that fades out the flyout and then sets it to GONE. */ @@ -1270,7 +1270,7 @@ public class BubbleStackView extends FrameLayout } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) - && mExpandedBubble != null; + && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null; if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show manage edu: " + shouldShow); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt index 063dac3d4109..ab194dfb3ce9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt @@ -24,8 +24,8 @@ import android.util.IntProperty import android.view.Gravity import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.WindowInsets +import android.view.WindowManager import android.widget.FrameLayout import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY @@ -41,6 +41,7 @@ class DismissView(context: Context) : FrameLayout(context) { var circle = DismissCircleView(context) var isShowing = false + var targetSizeResId: Int private val animator = PhysicsAnimator.getInstance(circle) private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) @@ -70,7 +71,8 @@ class DismissView(context: Context) : FrameLayout(context) { setVisibility(View.INVISIBLE) setBackgroundDrawable(gradientDrawable) - val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + targetSizeResId = R.dimen.dismiss_circle_size + val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) addView(circle, LayoutParams(targetSize, targetSize, Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) // start with circle offscreen so it's animated up @@ -126,7 +128,7 @@ class DismissView(context: Context) : FrameLayout(context) { layoutParams.height = resources.getDimensionPixelSize( R.dimen.floating_dismiss_gradient_height) - val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + val targetSize = resources.getDimensionPixelSize(targetSizeResId) circle.layoutParams.width = targetSize circle.layoutParams.height = targetSize circle.requestLayout() @@ -153,4 +155,4 @@ class DismissView(context: Context) : FrameLayout(context) { setPadding(0, 0, 0, navInset.bottom + resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java index c4bd73ba1b4a..fad3dee1f927 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common; import android.graphics.PixelFormat; import android.graphics.Rect; import android.view.SurfaceControl; +import android.window.ScreenCapture; import java.util.function.Consumer; @@ -28,16 +29,16 @@ import java.util.function.Consumer; public class ScreenshotUtils { /** - * Take a screenshot of the specified SurfaceControl. + * Takes a screenshot of the specified SurfaceControl. * * @param sc the SurfaceControl to take a screenshot of * @param crop the crop to use when capturing the screenshot * @param consumer Consumer for the captured buffer */ public static void captureLayer(SurfaceControl sc, Rect crop, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { - consumer.accept(SurfaceControl.captureLayers( - new SurfaceControl.LayerCaptureArgs.Builder(sc) + Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) { + consumer.accept(ScreenCapture.captureLayers( + new ScreenCapture.LayerCaptureArgs.Builder(sc) .setSourceCrop(crop) .setCaptureSecureLayers(true) .setAllowProtected(true) @@ -45,20 +46,23 @@ public class ScreenshotUtils { } private static class BufferConsumer implements - Consumer<SurfaceControl.ScreenshotHardwareBuffer> { + Consumer<ScreenCapture.ScreenshotHardwareBuffer> { SurfaceControl mScreenshot = null; SurfaceControl.Transaction mTransaction; SurfaceControl mSurfaceControl; + SurfaceControl mParentSurfaceControl; int mLayer; - BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, int layer) { + BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, SurfaceControl parentSc, + int layer) { mTransaction = t; mSurfaceControl = sc; + mParentSurfaceControl = parentSc; mLayer = layer; } @Override - public void accept(SurfaceControl.ScreenshotHardwareBuffer buffer) { + public void accept(ScreenCapture.ScreenshotHardwareBuffer buffer) { if (buffer == null || buffer.getHardwareBuffer() == null) { return; } @@ -72,7 +76,7 @@ public class ScreenshotUtils { mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer()); mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace()); - mTransaction.reparent(mScreenshot, mSurfaceControl); + mTransaction.reparent(mScreenshot, mParentSurfaceControl); mTransaction.setLayer(mScreenshot, mLayer); mTransaction.show(mScreenshot); mTransaction.apply(); @@ -80,7 +84,7 @@ public class ScreenshotUtils { } /** - * Take a screenshot of the specified SurfaceControl. + * Takes a screenshot of the specified SurfaceControl. * * @param t the transaction used to set changes on the resulting screenshot. * @param sc the SurfaceControl to take a screenshot of @@ -91,7 +95,23 @@ public class ScreenshotUtils { */ public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc, Rect crop, int layer) { - BufferConsumer consumer = new BufferConsumer(t, sc, layer); + return takeScreenshot(t, sc, sc /* parentSc */, crop, layer); + } + + /** + * Takes a screenshot of the specified SurfaceControl. + * + * @param t the transaction used to set changes on the resulting screenshot. + * @param sc the SurfaceControl to take a screenshot of + * @param parentSc the SurfaceControl to attach the screenshot to. + * @param crop the crop to use when capturing the screenshot + * @param layer the layer to place the screenshot + * + * @return A SurfaceControl where the screenshot will be attached, or null if failed. + */ + public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc, + SurfaceControl parentSc, Rect crop, int layer) { + BufferConsumer consumer = new BufferConsumer(t, sc, parentSc, layer); captureLayer(sc, crop, consumer); return consumer.mScreenshot; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 4c85d2090ec5..419e62daf586 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -561,6 +561,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (from == to) { // No animation run, still callback to stop resizing. mSplitLayoutHandler.onLayoutSizeChanged(this); + + if (flingFinishedCallback != null) { + flingFinishedCallback.run(); + } InteractionJankMonitorUtils.endTracing( CUJ_SPLIT_SCREEN_RESIZE); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index c39602032170..7c3c14e2210c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -55,6 +55,8 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; @@ -477,11 +479,12 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { return Optional.ofNullable( RecentTasksController.create(context, shellInit, shellCommandHandler, - taskStackListener, mainExecutor)); + taskStackListener, desktopModeTaskRepository, mainExecutor)); } // @@ -666,6 +669,24 @@ public abstract class WMShellBaseModule { } // + // Desktop mode (optional feature) + // + + @BindsOptionalOf + @DynamicOverride + abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); + + @WMSingleton + @Provides + static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository( + @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) { + if (DesktopMode.IS_SUPPORTED) { + return desktopModeTaskRepository; + } + return Optional.empty(); + } + + // // Misc // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 18ce3642335d..27d3e35fa07a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -51,6 +51,7 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; @@ -220,14 +221,14 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<RecentTasksController> recentTasksController, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, WindowDecorViewModel<?> windowDecorViewModel) { // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module ShellInit init = FreeformComponents.isFreeformEnabled(context) ? shellInit : null; - return new FreeformTaskListener<>(init, shellTaskOrganizer, recentTasksController, + return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository, windowDecorViewModel); } @@ -599,17 +600,24 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, RootDisplayAreaOrganizer rootDisplayAreaOrganizer, - @ShellMainThread Handler mainHandler + @ShellMainThread Handler mainHandler, + Transitions transitions ) { if (DesktopMode.IS_SUPPORTED) { return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer, - rootDisplayAreaOrganizer, - mainHandler)); + rootDisplayAreaOrganizer, mainHandler, transitions)); } else { return Optional.empty(); } } + @WMSingleton + @Provides + @DynamicOverride + static DesktopModeTaskRepository provideDesktopModeTaskRepository() { + return new DesktopModeTaskRepository(); + } + // // Misc // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index 7d34ea481de6..c07ce1065302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; @@ -37,6 +38,7 @@ import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; /** * Handles windowing changes when desktop mode system setting changes @@ -47,15 +49,18 @@ public class DesktopModeController { private final ShellTaskOrganizer mShellTaskOrganizer; private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; private final SettingsObserver mSettingsObserver; + private final Transitions mTransitions; public DesktopModeController(Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, RootDisplayAreaOrganizer rootDisplayAreaOrganizer, - @ShellMainThread Handler mainHandler) { + @ShellMainThread Handler mainHandler, + Transitions transitions) { mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; mSettingsObserver = new SettingsObserver(mContext, mainHandler); + mTransitions = transitions; shellInit.addInitCallback(this::onInit, this); } @@ -89,7 +94,11 @@ public class DesktopModeController { } wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId, targetWindowingMode), true /* transfer */); - mRootDisplayAreaOrganizer.applyTransaction(wct); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitions.startTransition(TRANSIT_CHANGE, wct, null); + } else { + mRootDisplayAreaOrganizer.applyTransaction(wct); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt new file mode 100644 index 000000000000..988601c0e8a8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.util.ArraySet + +/** + * Keeps track of task data related to desktop mode. + */ +class DesktopModeTaskRepository { + + /** + * Set of task ids that are marked as active in desktop mode. + * Active tasks in desktop mode are freeform tasks that are visible or have been visible after + * desktop mode was activated. + * Task gets removed from this list when it vanishes. Or when desktop mode is turned off. + */ + private val activeTasks = ArraySet<Int>() + private val listeners = ArraySet<Listener>() + + /** + * Add a [Listener] to be notified of updates to the repository. + */ + fun addListener(listener: Listener) { + listeners.add(listener) + } + + /** + * Remove a previously registered [Listener] + */ + fun removeListener(listener: Listener) { + listeners.remove(listener) + } + + /** + * Mark a task with given [taskId] as active. + */ + fun addActiveTask(taskId: Int) { + val added = activeTasks.add(taskId) + if (added) { + listeners.onEach { it.onActiveTasksChanged() } + } + } + + /** + * Remove task with given [taskId] from active tasks. + */ + fun removeActiveTask(taskId: Int) { + val removed = activeTasks.remove(taskId) + if (removed) { + listeners.onEach { it.onActiveTasksChanged() } + } + } + + /** + * Check if a task with the given [taskId] was marked as an active task + */ + fun isActiveTask(taskId: Int): Boolean { + return activeTasks.contains(taskId) + } + + /** + * Get a set of the active tasks + */ + fun getActiveTasks(): ArraySet<Int> { + return ArraySet(activeTasks) + } + + /** + * Defines interface for classes that can listen to changes in repository state. + */ + interface Listener { + fun onActiveTasksChanged() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 1baac718ee95..ac95d4bc63d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -29,8 +29,8 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -49,7 +49,7 @@ public class FreeformTaskListener<T extends AutoCloseable> private static final String TAG = "FreeformTaskListener"; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<RecentTasksController> mRecentTasksOptional; + private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; private final WindowDecorViewModel<T> mWindowDecorationViewModel; private final SparseArray<State<T>> mTasks = new SparseArray<>(); @@ -64,11 +64,11 @@ public class FreeformTaskListener<T extends AutoCloseable> public FreeformTaskListener( ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<RecentTasksController> recentTasksController, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, WindowDecorViewModel<T> windowDecorationViewModel) { mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; - mRecentTasksOptional = recentTasksController; + mDesktopModeTaskRepository = desktopModeTaskRepository; if (shellInit != null) { shellInit.addInitCallback(this::onInit, this); } @@ -93,7 +93,7 @@ public class FreeformTaskListener<T extends AutoCloseable> if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); - mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId)); } } @@ -126,7 +126,7 @@ public class FreeformTaskListener<T extends AutoCloseable> if (DesktopMode.IS_SUPPORTED) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); - mRecentTasksOptional.ifPresent(rt -> rt.removeActiveFreeformTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId)); } if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -154,7 +154,7 @@ public class FreeformTaskListener<T extends AutoCloseable> if (taskInfo.isVisible) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); - mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId)); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index e9f9bb5d7327..7d1259a732c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -16,8 +16,6 @@ package com.android.wm.shell.fullscreen; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; @@ -107,13 +105,14 @@ public class FullscreenTaskListener<T extends AutoCloseable> if (Transitions.ENABLE_SHELL_TRANSITIONS) return; updateRecentsForVisibleFullscreenTask(taskInfo); - if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) { + if (mWindowDecorViewModelOptional.isPresent()) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo, leash, t, t); t.apply(); - } else { + } + if (state.mWindowDecoration == null) { mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). @@ -178,13 +177,12 @@ public class FullscreenTaskListener<T extends AutoCloseable> public void createWindowDecoration(TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); - if (!mWindowDecorViewModelOptional.isPresent() - || !shouldShowWindowDecor(state.mTaskInfo)) { - return; - } - - state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration( + if (!mWindowDecorViewModelOptional.isPresent()) return; + T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration( state.mTaskInfo, state.mLeash, startT, finishT); + if (newWindowDecor != null) { + state.mWindowDecoration = newWindowDecor; + } } /** @@ -202,8 +200,7 @@ public class FullscreenTaskListener<T extends AutoCloseable> SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, @Nullable AutoCloseable windowDecor) { - if (!mWindowDecorViewModelOptional.isPresent() - || !shouldShowWindowDecor(change.getTaskInfo())) { + if (!mWindowDecorViewModelOptional.isPresent()) { return false; } final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); @@ -214,8 +211,11 @@ public class FullscreenTaskListener<T extends AutoCloseable> state.mTaskInfo, startT, finishT, state.mWindowDecoration); return true; } else { - state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration( + T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration( state.mTaskInfo, state.mLeash, startT, finishT); + if (newWindowDecor != null) { + state.mWindowDecoration = newWindowDecor; + } return false; } } @@ -256,7 +256,7 @@ public class FullscreenTaskListener<T extends AutoCloseable> windowDecor = mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId); } - if (mWindowDecorViewModelOptional.isPresent()) { + if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) { mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition( taskInfo, startT, finishT, windowDecor); } @@ -336,10 +336,5 @@ public class FullscreenTaskListener<T extends AutoCloseable> return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } - private static boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { - return taskInfo.getConfiguration().windowConfiguration.getDisplayWindowingMode() - == WINDOWING_MODE_FREEFORM; - } - } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java new file mode 100644 index 000000000000..acc0caf95e35 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip.phone; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.graphics.Rect; + +import com.android.wm.shell.pip.PipBoundsState; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Static utilities to get appropriate {@link PipDoubleTapHelper.PipSizeSpec} on a double tap. + */ +public class PipDoubleTapHelper { + + /** + * Should not be instantiated as a stateless class. + */ + private PipDoubleTapHelper() {} + + /** + * A constant that represents a pip screen size. + * + * <p>CUSTOM - user resized screen size (by pinching in/out)</p> + * <p>DEFAULT - normal screen size used as default when entering pip mode</p> + * <p>MAX - maximum allowed screen size</p> + */ + @IntDef(value = { + SIZE_SPEC_CUSTOM, + SIZE_SPEC_DEFAULT, + SIZE_SPEC_MAX + }) + @Retention(RetentionPolicy.SOURCE) + @interface PipSizeSpec {} + + static final int SIZE_SPEC_CUSTOM = 2; + static final int SIZE_SPEC_DEFAULT = 0; + static final int SIZE_SPEC_MAX = 1; + + /** + * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from. + * + * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and + * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between + * the latter two sizes is determined based on the current state of the pip screen.</p> + * + * @param mPipBoundsState current state of the pip screen + */ + @PipSizeSpec + private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) { + // determine the average pip screen width + int averageWidth = (mPipBoundsState.getMaxSize().x + + mPipBoundsState.getMinSize().x) / 2; + + // If pip screen width is above average, DEFAULT is the size spec we need to + // toggle to. Otherwise, we choose MAX. + return (mPipBoundsState.getBounds().width() > averageWidth) + ? SIZE_SPEC_DEFAULT + : SIZE_SPEC_MAX; + } + + /** + * Determines the {@link PipSizeSpec} to toggle to on double tap. + * + * @param mPipBoundsState current state of the pip screen + * @param userResizeBounds latest user resized bounds (by pinching in/out) + * @return pip screen size to switch to + */ + @PipSizeSpec + static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState, + @NonNull Rect userResizeBounds) { + // is pip screen at its maximum + boolean isScreenMax = mPipBoundsState.getBounds().width() + == mPipBoundsState.getMaxSize().x; + + // is pip screen at its normal default size + boolean isScreenDefault = (mPipBoundsState.getBounds().width() + == mPipBoundsState.getNormalBounds().width()) + && (mPipBoundsState.getBounds().height() + == mPipBoundsState.getNormalBounds().height()); + + // edge case 1 + // if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet + // or if user has resized exactly to DEFAULT, then we just want to maximize + if (isScreenDefault + && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) { + return SIZE_SPEC_MAX; + } + + // edge case 2 + // if user has maximized, then we want to toggle to DEFAULT + if (isScreenMax + && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) { + return SIZE_SPEC_DEFAULT; + } + + // otherwise in general we want to toggle back to user's CUSTOM size + if (isScreenDefault || isScreenMax) { + return SIZE_SPEC_CUSTOM; + } + + // if we are currently in user resized CUSTOM size state + // then we toggle either to MAX or DEFAULT depending on the current pip screen state + return getMaxOrDefaultPipSizeSpec(mPipBoundsState); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index a2fa058e97b7..84d9217e6fb3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -929,9 +929,18 @@ public class PipTouchHandler { if (mMenuController.isMenuVisible()) { mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); } - if (toExpand) { + + // the size to toggle to after a double tap + int nextSize = PipDoubleTapHelper + .nextSizeSpec(mPipBoundsState, getUserResizeBounds()); + + // actually toggle to the size chosen + if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) { mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); animateToMaximizedState(null); + } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) { + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + animateToNormalSize(null); } else { animateToUnexpandedState(getUserResizeBounds()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 27bc1a189086..ff4b2edb7310 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; @@ -53,19 +54,20 @@ import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; /** * Manages the recent task list from the system, caching it as necessary. */ public class RecentTasksController implements TaskStackListenerCallback, - RemoteCallable<RecentTasksController> { + RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener { private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; private final ShellCommandHandler mShellCommandHandler; + private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; private final ShellExecutor mMainExecutor; private final TaskStackListenerImpl mTaskStackListener; private final RecentTasks mImpl = new RecentTasksImpl(); @@ -84,15 +86,6 @@ public class RecentTasksController implements TaskStackListenerCallback, private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** - * Set of taskId's that have been launched in freeform mode. - * This includes tasks that are currently running, visible and in freeform mode. And also - * includes tasks that are running in the background, are no longer visible, but at some point - * were visible to the user. - * This is used to decide which freeform apps belong to the user's desktop. - */ - private final HashSet<Integer> mActiveFreeformTasks = new HashSet<>(); - - /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not * supported. */ @@ -102,24 +95,27 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellInit shellInit, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) { return null; } return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener, - mainExecutor); + desktopModeTaskRepository, mainExecutor); } RecentTasksController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, ShellExecutor mainExecutor) { mContext = context; mShellCommandHandler = shellCommandHandler; mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mTaskStackListener = taskStackListener; + mDesktopModeTaskRepository = desktopModeTaskRepository; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); } @@ -131,6 +127,7 @@ public class RecentTasksController implements TaskStackListenerCallback, private void onInit() { mShellCommandHandler.addDumpCallback(this::dump, this); mTaskStackListener.addListener(this); + mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this)); } /** @@ -217,19 +214,8 @@ public class RecentTasksController implements TaskStackListenerCallback, notifyRecentTasksChanged(); } - /** - * Mark a task with given {@code taskId} as active in freeform - */ - public void addActiveFreeformTask(int taskId) { - mActiveFreeformTasks.add(taskId); - notifyRecentTasksChanged(); - } - - /** - * Remove task with given {@code taskId} from active freeform tasks - */ - public void removeActiveFreeformTask(int taskId) { - mActiveFreeformTasks.remove(taskId); + @Override + public void onActiveTasksChanged() { notifyRecentTasksChanged(); } @@ -312,7 +298,8 @@ public class RecentTasksController implements TaskStackListenerCallback, continue; } - if (desktopModeActive && mActiveFreeformTasks.contains(taskInfo.taskId)) { + if (desktopModeActive && mDesktopModeTaskRepository.isPresent() + && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { // Freeform tasks will be added as a separate entry freeformTasks.add(taskInfo); continue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 3714fe713288..ecdafa9a63f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.pm.ShortcutInfo; import android.os.Bundle; import android.os.UserHandle; +import com.android.internal.logging.InstanceId; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.window.RemoteTransition; @@ -67,34 +68,35 @@ interface ISplitScreen { * Starts a shortcut in a stage. */ oneway void startShortcut(String packageName, String shortcutId, int position, - in Bundle options, in UserHandle user) = 8; + in Bundle options, in UserHandle user, in InstanceId instanceId) = 8; /** * Starts an activity in a stage. */ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int position, - in Bundle options) = 9; + in Bundle options, in InstanceId instanceId) = 9; /** * Starts tasks simultaneously in one transition. */ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, float splitRatio, - in RemoteTransition remoteTransition) = 10; + in RemoteTransition remoteTransition, in InstanceId instanceId) = 10; /** * Version of startTasks using legacy transition system. */ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, - float splitRatio, in RemoteAnimationAdapter adapter) = 11; + float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11; /** * Starts a pair of intent and task using legacy transition system. */ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions, - int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter) = 12; + int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter, + in InstanceId instanceId) = 12; /** * Blocking call that notifies and gets additional split-screen targets when entering @@ -115,5 +117,5 @@ interface ISplitScreen { */ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId, in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio, - in RemoteAnimationAdapter adapter) = 15; + in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index b0080b24c609..e7ec15e70c11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -45,11 +45,6 @@ class MainStage extends StageTaskListener { iconProvider); } - @Override - void dismiss(WindowContainerTransaction wct, boolean toTop) { - deactivate(wct, toTop); - } - boolean isActive() { return mIsActive; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 86efbe0af79c..8639b36faf4c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -42,11 +42,6 @@ class SideStage extends StageTaskListener { iconProvider); } - @Override - void dismiss(WindowContainerTransaction wct, boolean toTop) { - removeAllTasks(wct, toTop); - } - boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 9206afb00e27..025e5592e367 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -387,6 +387,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } + /** + * See {@link #startShortcut(String, String, int, Bundle, UserHandle)} + * @param instanceId to be used by {@link SplitscreenEventLogger} + */ + public void startShortcut(String packageName, String shortcutId, @SplitPosition int position, + @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) { + mStageCoordinator.getLogger().enterRequested(instanceId); + startShortcut(packageName, shortcutId, position, options, user); + } + + @Override public void startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user) { IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { @@ -415,7 +426,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 0 /* duration */, 0 /* statusBarTransitionDelay */); ActivityOptions activityOptions = ActivityOptions.fromBundle(options); activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); - try { LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, @@ -425,6 +435,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } + /** + * See {@link #startIntent(PendingIntent, Intent, int, Bundle)} + * @param instanceId to be used by {@link SplitscreenEventLogger} + */ + public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, + @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) { + mStageCoordinator.getLogger().enterRequested(instanceId); + startIntent(intent, fillInIntent, position, options); + } + + @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (fillInIntent == null) { @@ -446,7 +467,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options); return; } - mStageCoordinator.startIntent(intent, fillInIntent, position, options); } @@ -772,60 +792,64 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - float splitRatio, RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition, - splitRatio, adapter)); + splitRatio, adapter, instanceId)); } @Override public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions, - int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + int sidePosition, float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTaskWithLegacyTransition", (controller) -> controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition( pendingIntent, fillInIntent, taskId, mainOptions, sideOptions, - sidePosition, splitRatio, adapter)); + sidePosition, splitRatio, adapter, instanceId)); } @Override public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, - @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTaskWithLegacyTransition", (controller) -> controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition( shortcutInfo, taskId, mainOptions, sideOptions, sidePosition, - splitRatio, adapter)); + splitRatio, adapter, instanceId)); } @Override public void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, - @Nullable RemoteTransition remoteTransition) { + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions, - sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition)); + sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition, + instanceId)); } @Override public void startShortcut(String packageName, String shortcutId, int position, - @Nullable Bundle options, UserHandle user) { + @Nullable Bundle options, UserHandle user, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcut", (controller) -> { - controller.startShortcut(packageName, shortcutId, position, options, user); + controller.startShortcut(packageName, shortcutId, position, options, user, + instanceId); }); } @Override public void startIntent(PendingIntent intent, Intent fillInIntent, int position, - @Nullable Bundle options) { + @Nullable Bundle options, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntent", (controller) -> { - controller.startIntent(intent, fillInIntent, position, options); + controller.startIntent(intent, fillInIntent, position, options, instanceId); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java index 3e7a1004ed7a..626ccb1d2890 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java @@ -16,7 +16,7 @@ package com.android.wm.shell.splitscreen; -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; @@ -59,7 +59,7 @@ public class SplitscreenEventLogger { // Drag info private @SplitPosition int mDragEnterPosition; - private InstanceId mDragEnterSessionId; + private InstanceId mEnterSessionId; // For deduping async events private int mLastMainStagePosition = -1; @@ -82,9 +82,17 @@ public class SplitscreenEventLogger { /** * May be called before logEnter() to indicate that the session was started from a drag. */ - public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) { + public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) { mDragEnterPosition = position; - mDragEnterSessionId = dragSessionId; + enterRequested(enterSessionId); + } + + /** + * May be called before logEnter() to indicate that the session was started from launcher. + * This specifically is for all the scenarios where split started without a drag interaction + */ + public void enterRequested(InstanceId enterSessionId) { + mEnterSessionId = enterSessionId; } /** @@ -97,7 +105,7 @@ public class SplitscreenEventLogger { mLoggerSessionId = mIdSequence.newInstanceId(); int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape) - : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; + : SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER; updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), mainStageUid); updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), @@ -112,7 +120,7 @@ public class SplitscreenEventLogger { mLastMainStageUid, mLastSideStagePosition, mLastSideStageUid, - mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0, + mEnterSessionId != null ? mEnterSessionId.getId() : 0, mLoggerSessionId.getId()); } @@ -176,7 +184,7 @@ public class SplitscreenEventLogger { // Reset states mLoggerSessionId = null; mDragEnterPosition = SPLIT_POSITION_UNDEFINED; - mDragEnterSessionId = null; + mEnterSessionId = null; mLastMainStagePosition = -1; mLastMainStageUid = -1; mLastSideStagePosition = -1; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 8ed16ce4fbfc..db35b48eb898 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -69,6 +69,7 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.IActivityTaskManager; import android.app.PendingIntent; import android.app.WindowConfiguration; @@ -87,6 +88,7 @@ import android.util.Log; import android.util.Slog; import android.view.Choreographer; import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; @@ -121,6 +123,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; +import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.SplitBounds; @@ -201,6 +204,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + private DefaultMixedHandler mMixedHandler; + private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @Override @@ -324,6 +329,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, transitions.addHandler(this); } + public void setMixedHandler(DefaultMixedHandler mixedHandler) { + mMixedHandler = mixedHandler; + } + @VisibleForTesting SplitScreenTransitions getSplitTransitions() { return mSplitTransitions; @@ -405,6 +414,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return result; } + SplitscreenEventLogger getLogger() { + return mLogger; + } + /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -497,7 +510,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, - @Nullable RemoteTransition remoteTransition) { + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); mainOptions = mainOptions != null ? mainOptions : new Bundle(); sideOptions = sideOptions != null ? sideOptions : new Bundle(); @@ -526,46 +539,57 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null); + setEnterInstanceId(instanceId); } /** Starts 2 tasks in one legacy transition. */ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - float splitRatio, RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (sideOptions == null) sideOptions = new Bundle(); addActivityOptions(sideOptions, mSideStage); wct.startTask(sideTaskId, sideOptions); - startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter); + startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter, + instanceId); } /** Start an intent and a task ordered by {@code intentFirst}. */ void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, - @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (sideOptions == null) sideOptions = new Bundle(); addActivityOptions(sideOptions, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions); - startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter); + startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter, + instanceId); } void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions, - @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (sideOptions == null) sideOptions = new Bundle(); addActivityOptions(sideOptions, mSideStage); wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions); - startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter); + startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter, + instanceId); } - private void startWithLegacyTransition(WindowContainerTransaction sideWct, int mainTaskId, + /** + * @param instanceId if {@code null}, will not log. Otherwise it will be used in + * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} + */ + private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio, - RemoteAnimationAdapter adapter) { + RemoteAnimationAdapter adapter, InstanceId instanceId) { // Init divider first to make divider leash for remote animation target. mSplitLayout.init(); mSplitLayout.setDivideRatio(splitRatio); @@ -574,59 +598,56 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = false; mIsDividerRemoteAnimating = true; - LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() { - @Override - public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback, - SurfaceControl.Transaction t) { - if (apps == null || apps.length == 0) { - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - setDividerVisibility(true, t); - t.apply(); - onRemoteAnimationFinished(apps); - try { - adapter.getRunner().onAnimationCancelled(mKeyguardShowing); - } catch (RemoteException e) { - Slog.e(TAG, "Error starting remote animation", e); - } - return; - } - - // Wrap the divider bar into non-apps target to animate together. - nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, - getDividerBarLegacyTarget()); - - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - setDividerVisibility(true, t); - for (int i = 0; i < apps.length; ++i) { - if (apps[i].mode == MODE_OPENING) { - t.show(apps[i].leash); - // Reset the surface position of the opening app to prevent double-offset. - t.setPosition(apps[i].leash, 0, 0); - } - } - t.apply(); + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct); + prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct); + IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { IRemoteAnimationFinishedCallback wrapCallback = new IRemoteAnimationFinishedCallback.Stub() { @Override public void onAnimationFinished() throws RemoteException { - onRemoteAnimationFinished(apps); + onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct); finishedCallback.onAnimationFinished(); } }; Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); try { - adapter.getRunner().onAnimationStart( - transit, apps, wallpapers, nonApps, wrapCallback); + adapter.getRunner().onAnimationStart(transit, apps, wallpapers, + ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, + getDividerBarLegacyTarget()), wrapCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + } + + @Override + public void onAnimationCancelled(boolean isKeyguardOccluded) { + onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct); + try { + adapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { Slog.e(TAG, "Error starting remote animation", e); } } }; + RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( + wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); + + if (mainOptions == null) { + mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle(); + } else { + ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions); + mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); + mainOptions = mainActivityOptions.toBundle(); + } - final WindowContainerTransaction wct = new WindowContainerTransaction(); setSideStagePosition(sidePosition, wct); if (!mMainStage.isActive()) { mMainStage.activate(wct, false /* reparent */); @@ -634,34 +655,40 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mainOptions == null) mainOptions = new Bundle(); addActivityOptions(mainOptions, mMainStage); - wct.startTask(mainTaskId, mainOptions); - wct.merge(sideWct, true); - updateWindowBounds(mSplitLayout, wct); + wct.startTask(mainTaskId, mainOptions); wct.reorder(mRootTaskInfo.token, true); wct.setForceTranslucent(mRootTaskInfo.token, false); - mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + setDividerVisibility(true, t); + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + }); + + setEnterInstanceId(instanceId); + } + + private void setEnterInstanceId(InstanceId instanceId) { + if (instanceId != null) { + mLogger.enterRequested(instanceId); + } } - private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) { + private void onRemoteAnimationFinishedOrCancelled(boolean cancel, + WindowContainerTransaction evictWct) { mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; - if (apps == null || apps.length == 0) return; - - // If any stage has no child after finished animation, that side of the split will display - // nothing. This might happen if starting the same app on the both sides while not - // supporting multi-instance. Exit the split screen and expand that app to full screen. - if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { - mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 - ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); - return; + // If any stage has no child after animation finished, it means that split will display + // nothing, such status will happen if task and intent is same app but not support + // multi-instance, we should exit split and expand that app as full screen. + if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { + mMainExecutor.execute(() -> + exitSplitScreen(mMainStage.getChildCount() == 0 + ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + } else { + mSyncQueue.queue(evictWct); } - - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct); - prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct); - mSyncQueue.queue(evictWct); } /** @@ -907,13 +934,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Expand to top side split as full screen for fading out decor animation and dismiss // another side split(Moving its children to bottom). mIsExiting = true; - final StageTaskListener tempFullStage = childrenToTop; - final StageTaskListener dismissStage = mMainStage == childrenToTop - ? mSideStage : mMainStage; - tempFullStage.resetBounds(wct); - wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, + childrenToTop.resetBounds(wct); + wct.reorder(childrenToTop.mRootTaskInfo.token, true); + wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); - dismissStage.dismiss(wct, false /* toTop */); } mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { @@ -930,7 +954,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; - childrenToTop.dismiss(finishedWCT, true /* toTop */); + mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); + mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); @@ -1859,8 +1884,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Use normal animations. return false; + } else if (mMixedHandler != null && hasDisplayChange(info)) { + // A display-change has been un-expectedly inserted into the transition. Redirect + // handling to the mixed-handler to deal with splitting it up. + if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, + startTransaction, finishTransaction, finishCallback)) { + return true; + } } + return startPendingAnimation(transition, info, startTransaction, finishTransaction, + finishCallback); + } + + /** Starts the pending transition animation. */ + public boolean startPendingAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation( @@ -1879,6 +1921,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + private boolean hasDisplayChange(TransitionInfo info) { + boolean has = false; + for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) { + final TransitionInfo.Change change = info.getChanges().get(iC); + has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0; + } + return has; + } + /** Called to clean-up state and do house-keeping after the animation is done. */ public void onTransitionAnimationComplete() { // If still playing, let it finish. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 1af9415fca3a..6b90eabe3bd2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -106,11 +106,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } - /** - * General function for dismiss this stage. - */ - void dismiss(WindowContainerTransaction wct, boolean toTop) {} - int getChildCount() { return mChildrenTaskInfo.size(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index bcf4fbda0e0c..3cba92956f95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -17,6 +17,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -57,6 +58,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; + /** Both the display and split-state (enter/exit) is changing */ + static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -69,6 +73,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { Transitions.TransitionFinishCallback mFinishCallback = null; Transitions.TransitionHandler mLeftoversHandler = null; + WindowContainerTransaction mFinishWCT = null; /** * Mixed transitions are made up of multiple "parts". This keeps track of how many @@ -95,6 +100,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler(); mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); mPlayer.addHandler(this); + if (mSplitHandler != null) { + mSplitHandler.setMixedHandler(this); + } }, this); } } @@ -122,10 +130,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } private TransitionInfo subCopy(@NonNull TransitionInfo info, - @WindowManager.TransitionType int newType) { - final TransitionInfo out = new TransitionInfo(newType, info.getFlags()); - for (int i = 0; i < info.getChanges().size(); ++i) { - out.getChanges().add(info.getChanges().get(i)); + @WindowManager.TransitionType int newType, boolean withChanges) { + final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); + if (withChanges) { + for (int i = 0; i < info.getChanges().size(); ++i) { + out.getChanges().add(info.getChanges().get(i)); + } } out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y); out.setAnimationOptions(info.getAnimationOptions()); @@ -157,6 +167,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { + return false; } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " @@ -173,7 +185,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { + "entering PIP while Split-Screen is active."); TransitionInfo.Change pipChange = null; TransitionInfo.Change wallpaper = null; - final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK); + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); boolean homeIsOpening = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { TransitionInfo.Change change = info.getChanges().get(i); @@ -254,6 +266,87 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return true; } + private void unlinkMissingParents(TransitionInfo from) { + for (int i = 0; i < from.getChanges().size(); ++i) { + final TransitionInfo.Change chg = from.getChanges().get(i); + if (chg.getParent() == null) continue; + if (from.getChange(chg.getParent()) == null) { + from.getChanges().get(i).setParent(null); + } + } + } + + private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) { + TransitionInfo.Change curr = chg; + while (curr != null) { + if (curr.getTaskInfo() != null) return true; + if (curr.getParent() == null) break; + curr = info.getChange(curr.getParent()); + } + return false; + } + + /** + * This is intended to be called by SplitCoordinator as a helper to mix an already-pending + * split transition with a display-change. The use-case for this is when a display + * change/rotation gets collected into a split-screen enter/exit transition which has already + * been claimed by StageCoordinator.handleRequest . This happens during launcher tests. + */ + public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */); + final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (isWithinTask(info, change)) continue; + displayPart.addChange(change); + everythingElse.getChanges().remove(i); + } + if (displayPart.getChanges().isEmpty()) return false; + unlinkMissingParents(everythingElse); + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); + mixed.mFinishCallback = finishCallback; + mActiveTransitions.add(mixed); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " + + "and split change."); + // We need to split the transition into 2 parts: the split part and the display part. + mixed.mInFlightSubAnimations = 2; + + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + --mixed.mInFlightSubAnimations; + if (wctCB != null) { + throw new IllegalArgumentException("Can't mix transitions that require finish" + + " sync callback"); + } + if (wct != null) { + if (mixed.mFinishWCT == null) { + mixed.mFinishWCT = wct; + } else { + mixed.mFinishWCT.merge(wct, true /* transfer */); + } + } + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */); + }; + + // Dispatch the display change. This will most-likely be taken by the default handler. + // Do this first since the first handler used will apply the startT; the display change + // needs to take a screenshot before that happens so we need it to be the first handler. + mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart, + startT, finishT, finishCB, mSplitHandler); + + // Note: at this point, startT has probably already been applied, so we are basically + // giving splitHandler an empty startT. This is currently OK because display-change will + // grab a screenshot and paste it on top anyways. + mSplitHandler.startPendingAnimation( + transition, everythingElse, startT, finishT, finishCB); + return true; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -279,6 +372,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else { mPipHandler.end(); } + } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { + // queue } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 4c927b6e84b8..1ae779e25bbf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -96,6 +96,7 @@ import android.view.WindowManager.TransitionType; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Transformation; +import android.window.ScreenCapture; import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; @@ -630,16 +631,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { .setBufferSize(extensionRect.width(), extensionRect.height()) .build(); - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) + ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend) .setSourceCrop(edgeBounds) .setFrameScale(1) .setPixelFormat(PixelFormat.RGBA_8888) .setChildrenOnly(true) .setAllowProtected(true) .build(); - final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = - SurfaceControl.captureLayers(captureArgs); + final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = + ScreenCapture.captureLayers(captureArgs); if (edgeBuffer == null) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index b647f43da522..2b27baeb515a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -46,6 +46,7 @@ import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.window.ScreenCapture; import android.window.TransitionInfo; import com.android.internal.R; @@ -144,14 +145,14 @@ class ScreenRotationAnimation { t.reparent(mScreenshotLayer, mAnimLeash); mStartLuma = change.getSnapshotLuma(); } else { - SurfaceControl.LayerCaptureArgs args = - new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl) + ScreenCapture.LayerCaptureArgs args = + new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl) .setCaptureSecureLayers(true) .setAllowProtected(true) .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight)) .build(); - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureLayers(args); + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureLayers(args); if (screenshotBuffer == null) { Slog.w(TAG, "Unable to take screenshot of display"); return; @@ -481,8 +482,8 @@ class ScreenRotationAnimation { } Rect crop = new Rect(0, 0, bounds.width(), bounds.height()); - SurfaceControl.ScreenshotHardwareBuffer buffer = - SurfaceControl.captureLayers(surfaceControl, crop, 1); + ScreenCapture.ScreenshotHardwareBuffer buffer = + ScreenCapture.captureLayers(surfaceControl, crop, 1); if (buffer == null) { return 0; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 83aa539d24d6..e8a2cb160880 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -36,6 +36,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.transition.Transitions; @@ -80,6 +81,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + if (!shouldShowWindowDecor(taskInfo)) return null; final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration( mContext, mDisplayController, @@ -101,9 +103,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption @Override public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) { - return (windowDecor instanceof CaptionWindowDecoration) - ? (CaptionWindowDecoration) windowDecor - : null; + if (!(windowDecor instanceof CaptionWindowDecoration)) return null; + final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor; + if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) { + return null; + } + return captionWindowDecor; } @Override @@ -231,4 +236,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption } } } + + private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { + if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; + return DesktopMode.IS_SUPPORTED + && mDisplayController.getDisplayContext(taskInfo.displayId) + .getResources().getConfiguration().smallestScreenWidthDp >= 600; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index c234949572bf..d9697d288ab6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -42,6 +42,7 @@ public interface WindowDecorViewModel<T extends AutoCloseable> { /** * Creates a window decoration for the given task. + * Can be {@code null} for Fullscreen tasks but not Freeform ones. * * @param taskInfo the initial task info of the task * @param taskSurface the surface of the task @@ -49,7 +50,7 @@ public interface WindowDecorViewModel<T extends AutoCloseable> { * @param finishT the finish transaction to restore states after the transition * @return the window decoration object */ - T createWindowDecoration( + @Nullable T createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, @@ -57,11 +58,12 @@ public interface WindowDecorViewModel<T extends AutoCloseable> { /** * Adopts the window decoration if possible. + * May be {@code null} if a window decor is not needed or the given one is incompatible. * * @param windowDecor the potential window decoration to adopt * @return the window decoration if it can be adopted, or {@code null} otherwise. */ - T adoptWindowDecoration(@Nullable AutoCloseable windowDecor); + @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor); /** * Notifies a task info update on the given task, with the window decoration created previously diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index b2e45a6b3a5c..a7234c1d3cb8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -17,7 +17,7 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -57,7 +57,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim public void testStartAnimation() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); info.addChange(embeddingChange); doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index 84befdddabdb..3792e8361284 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -16,6 +16,9 @@ package com.android.wm.shell.activityembedding; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertNotNull; @@ -24,6 +27,8 @@ import static org.mockito.Mockito.mock; import android.animation.Animator; import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -80,4 +85,23 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { return new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); } + + /** + * Creates a mock {@link TransitionInfo.Change} with + * {@link TransitionInfo#FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY} flag. + */ + static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect taskBounds) { + final TransitionInfo.Change change = createChange(); + change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + change.setStartAbsBounds(startBounds); + change.setEndAbsBounds(endBounds); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + change.setFlags(FLAG_FILLS_TASK); + } + return change; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index cf43b0030d2a..baecf6fe6673 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -17,7 +17,6 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -29,6 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.graphics.Rect; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -48,6 +48,10 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase { + private static final Rect TASK_BOUNDS = new Rect(0, 0, 1000, 500); + private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500); + private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500); + @Before public void setup() { super.setUp(); @@ -77,13 +81,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); final TransitionInfo.Change nonEmbeddingChange = createChange(); info.addChange(embeddingChange); info.addChange(nonEmbeddingChange); - // No-op + // No-op because it contains non-embedded change. assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); @@ -93,13 +97,65 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation } @Test - public void testStartAnimation_onlyActivityEmbeddingChange() { + public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS, + TASK_BOUNDS); + info.addChange(embeddingChange); + + // No-op because it only contains embedded change that fills the Task. We will let the + // default handler to animate such transition. + assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); + verifyNoMoreInteractions(mStartTransaction); + verifyNoMoreInteractions(mFinishTransaction); + verifyNoMoreInteractions(mFinishCallback); + } + + @Test + public void testStartAnimation_containsActivityEmbeddingSplitChange() { + // Change that occupies only part of the Task. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() { + // Change that is entering ActivityEmbedding split. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() { + // Change that is exiting ActivityEmbedding split. final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, + TASK_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); - // No-op + // ActivityEmbeddingController will handle such transition. assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, @@ -115,8 +171,8 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation () -> mController.onAnimationFinished(mTransition)); final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index ef532e449bd6..577942505b13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; @@ -63,6 +64,8 @@ public class DesktopModeControllerTest extends ShellTestCase { private ShellExecutor mTestExecutor; @Mock private Handler mMockHandler; + @Mock + private Transitions mMockTransitions; private DesktopModeController mController; private ShellInit mShellInit; @@ -72,7 +75,7 @@ public class DesktopModeControllerTest extends ShellTestCase { mShellInit = Mockito.spy(new ShellInit(mTestExecutor)); mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer, - mRootDisplayAreaOrganizer, mMockHandler); + mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions); mShellInit.init(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt new file mode 100644 index 000000000000..9b28d11f6a9d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopModeTaskRepositoryTest : ShellTestCase() { + + private lateinit var repo: DesktopModeTaskRepository + + @Before + fun setUp() { + repo = DesktopModeTaskRepository() + } + + @Test + fun addActiveTask_listenerNotifiedAndTaskIsActive() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + assertThat(repo.isActiveTask(1)).isTrue() + } + + @Test + fun addActiveTask_sameTaskDoesNotNotify() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + repo.addActiveTask(1) + assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + } + + @Test + fun addActiveTask_multipleTasksAddedNotifiesForEach() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + repo.addActiveTask(2) + assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + } + + @Test + fun removeActiveTask_listenerNotifiedAndTaskNotActive() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + repo.removeActiveTask(1) + // Notify once for add and once for remove + assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + assertThat(repo.isActiveTask(1)).isFalse() + } + + @Test + fun removeActiveTask_removeNotExistingTaskDoesNotNotify() { + val listener = TestListener() + repo.addListener(listener) + repo.removeActiveTask(99) + assertThat(listener.activeTaskChangedCalls).isEqualTo(0) + } + + @Test + fun isActiveTask_notExistingTaskReturnsFalse() { + assertThat(repo.isActiveTask(99)).isFalse() + } + + class TestListener : DesktopModeTaskRepository.Listener { + var activeTaskChangedCalls = 0 + override fun onActiveTasksChanged() { + activeTaskChangedCalls++ + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java new file mode 100644 index 000000000000..8ce3ca4bdc00 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip.phone; + +import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM; +import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT; +import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX; +import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.graphics.Point; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.pip.PipBoundsState; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** + * Unit test against {@link PipDoubleTapHelper}. + */ +@RunWith(AndroidTestingRunner.class) +public class PipDoubleTapHelperTest extends ShellTestCase { + // represents the current pip window state and has information on current + // max, min, and normal sizes + @Mock private PipBoundsState mBoundStateMock; + // tied to boundsStateMock.getBounds() in setUp() + @Mock private Rect mBoundsMock; + + // represents the most recent manually resized bounds + // i.e. dimensions from the most recent pinch in/out + @Mock private Rect mUserResizeBoundsMock; + + // actual dimensions of the pip screen bounds + private static final int MAX_WIDTH = 100; + private static final int DEFAULT_WIDTH = 40; + private static final int MIN_WIDTH = 10; + + private static final int AVERAGE_WIDTH = (MAX_WIDTH + MIN_WIDTH) / 2; + + /** + * Initializes mocks and assigns values for different pip screen bounds. + */ + @Before + public void setUp() { + // define pip bounds + when(mBoundStateMock.getMaxSize()).thenReturn(new Point(MAX_WIDTH, 20)); + when(mBoundStateMock.getMinSize()).thenReturn(new Point(MIN_WIDTH, 2)); + + Rect rectMock = mock(Rect.class); + when(rectMock.width()).thenReturn(DEFAULT_WIDTH); + when(mBoundStateMock.getNormalBounds()).thenReturn(rectMock); + + when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH); + when(mBoundStateMock.getBounds()).thenReturn(mBoundsMock); + } + + /** + * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. + * + * <p>when the user resizes the screen to a larger than the average but not the maximum width, + * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.DEFAULT} + */ + @Test + public void testNextScreenSize_resizedWiderThanAverage_returnDefaultThenCustom() { + // make the user resize width in between MAX and average + when(mUserResizeBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2); + // make current bounds same as resized bound since no double tap yet + when(mBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2); + + // then nextScreenSize() i.e. double tapping should + // toggle to DEFAULT state + Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), + SIZE_SPEC_DEFAULT); + + // once we toggle to DEFAULT our screen size gets updated + // but not the user resize bounds + when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH); + + // then nextScreenSize() i.e. double tapping should + // toggle to CUSTOM state + Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), + SIZE_SPEC_CUSTOM); + } + + /** + * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. + * + * <p>when the user resizes the screen to a smaller than the average but not the default width, + * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.MAX} + */ + @Test + public void testNextScreenSize_resizedNarrowerThanAverage_returnMaxThenCustom() { + // make the user resize width in between MIN and average + when(mUserResizeBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2); + // make current bounds same as resized bound since no double tap yet + when(mBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2); + + // then nextScreenSize() i.e. double tapping should + // toggle to MAX state + Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), + SIZE_SPEC_MAX); + + // once we toggle to MAX our screen size gets updated + // but not the user resize bounds + when(mBoundsMock.width()).thenReturn(MAX_WIDTH); + + // then nextScreenSize() i.e. double tapping should + // toggle to CUSTOM state + Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), + SIZE_SPEC_CUSTOM); + } + + /** + * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. + * + * <p>when the user resizes the screen to exactly the maximum width + * then we toggle to {@code PipSizeSpec.DEFAULT} + */ + @Test + public void testNextScreenSize_resizedToMax_returnDefault() { + // the resized width is the same as MAX_WIDTH + when(mUserResizeBoundsMock.width()).thenReturn(MAX_WIDTH); + // the current bounds are also at MAX_WIDTH + when(mBoundsMock.width()).thenReturn(MAX_WIDTH); + + // then nextScreenSize() i.e. double tapping should + // toggle to DEFAULT state + Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), + SIZE_SPEC_DEFAULT); + } + + /** + * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. + * + * <p>when the user resizes the screen to exactly the default width + * then we toggle to {@code PipSizeSpec.MAX} + */ + @Test + public void testNextScreenSize_resizedToDefault_returnMax() { + // the resized width is the same as DEFAULT_WIDTH + when(mUserResizeBoundsMock.width()).thenReturn(DEFAULT_WIDTH); + // the current bounds are also at DEFAULT_WIDTH + when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH); + + // then nextScreenSize() i.e. double tapping should + // toggle to MAX state + Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), + SIZE_SPEC_MAX); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index e9a1e2523a86..cadfeb0de312 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -55,6 +55,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -82,6 +83,8 @@ public class RecentTasksControllerTest extends ShellTestCase { private TaskStackListenerImpl mTaskStackListener; @Mock private ShellCommandHandler mShellCommandHandler; + @Mock + private DesktopModeTaskRepository mDesktopModeTaskRepository; private ShellTaskOrganizer mShellTaskOrganizer; private RecentTasksController mRecentTasksController; @@ -94,7 +97,8 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit, - mShellCommandHandler, mTaskStackListener, mMainExecutor)); + mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository), + mMainExecutor)); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), mMainExecutor); @@ -195,8 +199,8 @@ public class RecentTasksControllerTest extends ShellTestCase { ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - mRecentTasksController.addActiveFreeformTask(1); - mRecentTasksController.addActiveFreeformTask(3); + when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index ea9390ee8efd..9240abfbe47f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -225,7 +225,6 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); verify(mMainStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); - verify(mSideStage).dismiss(any(WindowContainerTransaction.class), eq(false)); verify(mMainStage).resetBounds(any(WindowContainerTransaction.class)); } @@ -239,7 +238,6 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSideStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); verify(mSideStage).resetBounds(any(WindowContainerTransaction.class)); - verify(mMainStage).dismiss(any(WindowContainerTransaction.class), eq(false)); } @Test diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 39c7d198fe5b..235700b27c25 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -601,10 +601,6 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( return base::unexpected(result.error()); } - if (type_idx == 0x1c) { - LOG(ERROR) << base::StringPrintf("foobar first result %s", result->package_name->c_str()); - } - bool overlaid = false; if (!stop_at_first_match && !ignore_configuration && !apk_assets_[result->cookie]->IsLoader()) { for (const auto& id_map : package_group.overlays_) { @@ -615,7 +611,21 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( } if (overlay_entry.IsInlineValue()) { // The target resource is overlaid by an inline value not represented by a resource. - result->entry = overlay_entry.GetInlineValue(); + ConfigDescription best_frro_config; + Res_value best_frro_value; + bool frro_found = false; + for( const auto& [config, value] : overlay_entry.GetInlineValue()) { + if ((!frro_found || config.isBetterThan(best_frro_config, desired_config)) + && config.match(*desired_config)) { + frro_found = true; + best_frro_config = config; + best_frro_value = value; + } + } + if (!frro_found) { + continue; + } + result->entry = best_frro_value; result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable(); result->cookie = id_map.cookie; diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index efd1f6a25786..e122d4844ee9 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -56,6 +56,8 @@ struct Idmap_header { struct Idmap_data_header { uint32_t target_entry_count; uint32_t target_inline_entry_count; + uint32_t target_inline_entry_value_count; + uint32_t configuration_count; uint32_t overlay_entry_count; uint32_t string_pool_index_offset; @@ -68,6 +70,12 @@ struct Idmap_target_entry { struct Idmap_target_entry_inline { uint32_t target_id; + uint32_t start_value_index; + uint32_t value_count; +}; + +struct Idmap_target_entry_inline_value { + uint32_t config_index; Res_value value; }; @@ -138,11 +146,15 @@ status_t OverlayDynamicRefTable::lookupResourceIdNoRewrite(uint32_t* resId) cons IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, const Idmap_target_entry* entries, const Idmap_target_entry_inline* inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values, + const ConfigDescription* configs, uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) : data_header_(data_header), entries_(entries), inline_entries_(inline_entries), + inline_entry_values_(inline_entry_values), + configurations_(configs), target_assigned_package_id_(target_assigned_package_id), overlay_ref_table_(overlay_ref_table) { } @@ -183,7 +195,13 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { if (inline_entry != end_inline_entry && (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) { - return Result(inline_entry->value); + std::map<ConfigDescription, Res_value> values_map; + for (int i = 0; i < inline_entry->value_count; i++) { + const auto& value = inline_entry_values_[inline_entry->start_value_index + i]; + const auto& config = configurations_[value.config_index]; + values_map[config] = value.value; + } + return Result(values_map); } return {}; } @@ -237,6 +255,8 @@ LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values, + const ConfigDescription* configs, const Idmap_overlay_entry* overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, std::string_view overlay_apk_path, @@ -245,6 +265,8 @@ LoadedIdmap::LoadedIdmap(std::string&& idmap_path, data_header_(data_header), target_entries_(target_entries), target_inline_entries_(target_inline_entries), + inline_entry_values_(inline_entry_values), + configurations_(configs), overlay_entries_(overlay_entries), string_pool_(std::move(string_pool)), idmap_path_(std::move(idmap_path)), @@ -303,6 +325,21 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, if (target_inline_entries == nullptr) { return {}; } + + auto target_inline_entry_values = ReadType<Idmap_target_entry_inline_value>( + &data_ptr, &data_size, "target inline values", + dtohl(data_header->target_inline_entry_value_count)); + if (target_inline_entry_values == nullptr) { + return {}; + } + + auto configurations = ReadType<ConfigDescription>( + &data_ptr, &data_size, "configurations", + dtohl(data_header->configuration_count)); + if (configurations == nullptr) { + return {}; + } + auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline", dtohl(data_header->overlay_entry_count)); if (overlay_entries == nullptr) { @@ -329,8 +366,8 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, // Can't use make_unique because LoadedIdmap constructor is private. return std::unique_ptr<LoadedIdmap>( new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries, - target_inline_entries, overlay_entries, std::move(idmap_string_pool), - *target_path, *overlay_path)); + target_inline_entries, target_inline_entry_values, configurations, + overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path)); } bool LoadedIdmap::IsUpToDate() const { diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index 6804472b3d17..a1cbbbf2271b 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -23,6 +23,7 @@ #include <variant> #include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" #include "androidfw/StringPiece.h" #include "androidfw/ResourceTypes.h" #include "utils/ByteOrder.h" @@ -35,6 +36,7 @@ struct Idmap_header; struct Idmap_data_header; struct Idmap_target_entry; struct Idmap_target_entry_inline; +struct Idmap_target_entry_inline_value; struct Idmap_overlay_entry; // A string pool for overlay apk assets. The string pool holds the strings of the overlay resources @@ -91,7 +93,8 @@ class IdmapResMap { public: Result() = default; explicit Result(uint32_t value) : data_(value) {}; - explicit Result(const Res_value& value) : data_(value) { }; + explicit Result(const std::map<ConfigDescription, Res_value> &value) + : data_(value) { }; // Returns `true` if the resource is overlaid. explicit operator bool() const { @@ -107,15 +110,16 @@ class IdmapResMap { } bool IsInlineValue() const { - return std::get_if<Res_value>(&data_) != nullptr; + return std::get_if<2>(&data_) != nullptr; } - const Res_value& GetInlineValue() const { - return std::get<Res_value>(data_); + const std::map<ConfigDescription, Res_value>& GetInlineValue() const { + return std::get<2>(data_); } private: - std::variant<std::monostate, uint32_t, Res_value> data_; + std::variant<std::monostate, uint32_t, + std::map<ConfigDescription, Res_value> > data_; }; // Looks up the value that overlays the target resource id. @@ -129,12 +133,16 @@ class IdmapResMap { explicit IdmapResMap(const Idmap_data_header* data_header, const Idmap_target_entry* entries, const Idmap_target_entry_inline* inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values, + const ConfigDescription* configs, uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table); const Idmap_data_header* data_header_; const Idmap_target_entry* entries_; const Idmap_target_entry_inline* inline_entries_; + const Idmap_target_entry_inline_value* inline_entry_values_; + const ConfigDescription* configurations_; const uint8_t target_assigned_package_id_; const OverlayDynamicRefTable* overlay_ref_table_; @@ -170,8 +178,8 @@ class LoadedIdmap { // Returns a mapping from target resource ids to overlay values. const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const { - return IdmapResMap(data_header_, target_entries_, target_inline_entries_, - target_assigned_package_id, overlay_ref_table); + return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_, + configurations_, target_assigned_package_id, overlay_ref_table); } // Returns a dynamic reference table for a loaded overlay package. @@ -191,6 +199,8 @@ class LoadedIdmap { const Idmap_data_header* data_header_; const Idmap_target_entry* target_entries_; const Idmap_target_entry_inline* target_inline_entries_; + const Idmap_target_entry_inline_value* inline_entry_values_; + const ConfigDescription* configurations_; const Idmap_overlay_entry* overlay_entries_; const std::unique_ptr<ResStringPool> string_pool_; @@ -207,6 +217,8 @@ class LoadedIdmap { const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, + const Idmap_target_entry_inline_value* inline_entry_values_, + const ConfigDescription* configs, const Idmap_overlay_entry* overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, std::string_view overlay_apk_path, diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 8c614bcf7145..9309091f4124 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -45,7 +45,7 @@ namespace android { constexpr const uint32_t kIdmapMagic = 0x504D4449u; -constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u; +constexpr const uint32_t kIdmapCurrentVersion = 0x00000009u; // This must never change. constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian) @@ -1098,7 +1098,7 @@ struct ResTable_config SDKVERSION_ANY = 0 }; - enum { + enum { MINORVERSION_ANY = 0 }; diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap Binary files differindex 88eadccb38cf..8e847e81aa31 100644 --- a/libs/androidfw/tests/data/overlay/overlay.idmap +++ b/libs/androidfw/tests/data/overlay/overlay.idmap diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 397975dcb78f..473afbd2aa2f 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -18,6 +18,7 @@ #include "CanvasProperty.h" #include "NinePatchUtils.h" +#include "SkBlendMode.h" #include "VectorDrawable.h" #include "hwui/Bitmap.h" #include "hwui/MinikinUtils.h" @@ -251,10 +252,11 @@ const SkiaCanvas::SaveRec* SkiaCanvas::currentSaveRec() const { return (rec && rec->saveCount == currentSaveCount) ? rec : nullptr; } -void SkiaCanvas::punchHole(const SkRRect& rect) { +void SkiaCanvas::punchHole(const SkRRect& rect, float alpha) { SkPaint paint = SkPaint(); - paint.setColor(0); - paint.setBlendMode(SkBlendMode::kClear); + paint.setColor(SkColors::kBlack); + paint.setAlphaf(alpha); + paint.setBlendMode(SkBlendMode::kDstOut); mCanvas->drawRRect(rect, paint); } diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index c6313f6c3a88..51007c52260d 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -65,7 +65,7 @@ public: LOG_ALWAYS_FATAL("SkiaCanvas does not support enableZ"); } - virtual void punchHole(const SkRRect& rect) override; + virtual void punchHole(const SkRRect& rect, float alpha) override; virtual void setBitmap(const SkBitmap& bitmap) override; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 7378351ef771..82d23b51b12a 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -152,7 +152,7 @@ public: LOG_ALWAYS_FATAL("Not supported"); } - virtual void punchHole(const SkRRect& rect) = 0; + virtual void punchHole(const SkRRect& rect, float alpha) = 0; // ---------------------------------------------------------------------------- // Canvas state operations diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index fb7d5f72744d..0513447ed05e 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -713,9 +713,10 @@ static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) { } static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right, - jfloat bottom, jfloat rx, jfloat ry) { + jfloat bottom, jfloat rx, jfloat ry, jfloat alpha) { auto canvas = reinterpret_cast<Canvas*>(canvasPtr); - canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry)); + canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry), + alpha); } }; // namespace CanvasJNI @@ -790,7 +791,7 @@ static const JNINativeMethod gDrawMethods[] = { {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString}, {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars}, {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString}, - {"nPunchHole", "(JFFFFFF)V", (void*) CanvasJNI::punchHole} + {"nPunchHole", "(JFFFFFFF)V", (void*) CanvasJNI::punchHole} }; int register_android_graphics_Canvas(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 3bf2b2e63a47..f2282e661535 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -201,6 +201,7 @@ protected: paint.setAlpha((uint8_t)paint.getAlpha() * mAlpha); return true; } + void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { // We unroll the drawable using "this" canvas, so that draw calls contained inside will // get their alpha applied. The default SkPaintFilterCanvas::onDrawDrawable does not unroll. @@ -292,7 +293,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // with the same canvas transformation + clip into the target // canvas then draw the layer on top if (renderNode->hasHolePunches()) { - TransformCanvas transformCanvas(canvas, SkBlendMode::kClear); + TransformCanvas transformCanvas(canvas, SkBlendMode::kDstOut); displayList->draw(&transformCanvas); } canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds), diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 5c6117d86415..1f87865f2672 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -69,20 +69,22 @@ void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, in mDisplayList->setHasHolePunches(false); } -void SkiaRecordingCanvas::punchHole(const SkRRect& rect) { - // Add the marker annotation to allow HWUI to determine where the current - // clip/transformation should be applied +void SkiaRecordingCanvas::punchHole(const SkRRect& rect, float alpha) { + // Add the marker annotation to allow HWUI to determine the current + // clip/transformation and alpha should be applied SkVector vector = rect.getSimpleRadii(); - float data[2]; + float data[3]; data[0] = vector.x(); data[1] = vector.y(); + data[2] = alpha; mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(), - SkData::MakeWithCopy(data, 2 * sizeof(float))); + SkData::MakeWithCopy(data, sizeof(data))); // Clear the current rect within the layer itself SkPaint paint = SkPaint(); - paint.setColor(0); - paint.setBlendMode(SkBlendMode::kClear); + paint.setColor(SkColors::kBlack); + paint.setAlphaf(alpha); + paint.setBlendMode(SkBlendMode::kDstOut); mRecorder.drawRRect(rect, paint); mDisplayList->setHasHolePunches(true); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 89e3a2c24e1e..7844e2cc2a73 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -50,7 +50,7 @@ public: initDisplayList(renderNode, width, height); } - virtual void punchHole(const SkRRect& rect) override; + virtual void punchHole(const SkRRect& rect, float alpha) override; virtual void finishRecording(uirenderer::RenderNode* destination) override; std::unique_ptr<SkiaDisplayList> finishRecording(); diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index 33160d05e2d0..c320df035d08 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -29,13 +29,15 @@ using namespace android::uirenderer::skiapipeline; void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) { if (HOLE_PUNCH_ANNOTATION == key) { auto* rectParams = reinterpret_cast<const float*>(value->data()); - float radiusX = rectParams[0]; - float radiusY = rectParams[1]; + const float radiusX = rectParams[0]; + const float radiusY = rectParams[1]; + const float alpha = rectParams[2]; SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY); SkPaint paint; paint.setColor(SkColors::kBlack); paint.setBlendMode(mHolePunchBlendMode); + paint.setAlphaf(alpha); mWrappedCanvas->drawRRect(roundRect, paint); } } diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp index 59230a754f4e..7d3ca9642458 100644 --- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp @@ -138,7 +138,7 @@ private: roundRectPaint.setColor(Color::White); if (addHolePunch) { // Punch a hole but then cover it up, we don't want to actually see it - canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight))); + canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)), 1.f); } canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); @@ -235,4 +235,4 @@ class StretchyUniformLayerListViewHolePunch : public StretchyListViewAnimation { StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; } bool haveHolePunch() override { return true; } bool forceLayer() override { return true; } -};
\ No newline at end of file +}; diff --git a/location/java/android/location/LocationTime.java b/location/java/android/location/LocationTime.java index e5535d192776..2f03508fbb15 100644 --- a/location/java/android/location/LocationTime.java +++ b/location/java/android/location/LocationTime.java @@ -20,28 +20,32 @@ import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import java.time.Duration; +import java.time.Instant; + /** - * Data class for passing location derived time. + * Data class for passing GNSS-derived time. * @hide */ public final class LocationTime implements Parcelable { - private final long mTime; + private final long mUnixEpochTimeMillis; private final long mElapsedRealtimeNanos; - public LocationTime(long time, long elapsedRealtimeNanos) { - mTime = time; + public LocationTime(long unixEpochTimeMillis, long elapsedRealtimeNanos) { + mUnixEpochTimeMillis = unixEpochTimeMillis; mElapsedRealtimeNanos = elapsedRealtimeNanos; } /** - * The current time, according to the Gnss location provider. */ - public long getTime() { - return mTime; + * The Unix epoch time in millis, according to the Gnss location provider. + */ + public long getUnixEpochTimeMillis() { + return mUnixEpochTimeMillis; } /** - * The elapsed nanos since boot {@link #getTime} was computed at. + * The elapsed nanos since boot when {@link #getUnixEpochTimeMillis} was the current time. */ public long getElapsedRealtimeNanos() { return mElapsedRealtimeNanos; @@ -49,7 +53,7 @@ public final class LocationTime implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeLong(mTime); + out.writeLong(mUnixEpochTimeMillis); out.writeLong(mElapsedRealtimeNanos); } @@ -58,8 +62,18 @@ public final class LocationTime implements Parcelable { return 0; } + @Override + public String toString() { + return "LocationTime{" + + "mUnixEpochTimeMillis=" + Instant.ofEpochMilli(mUnixEpochTimeMillis) + + "(" + mUnixEpochTimeMillis + ")" + + ", mElapsedRealtimeNanos=" + Duration.ofNanos(mElapsedRealtimeNanos) + + "(" + mElapsedRealtimeNanos + ")" + + '}'; + } + public static final @NonNull Parcelable.Creator<LocationTime> CREATOR = - new Parcelable.Creator<LocationTime>() { + new Parcelable.Creator<>() { public LocationTime createFromParcel(Parcel in) { long time = in.readLong(); long elapsedRealtimeNanos = in.readLong(); diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 60c812ad048f..819358b49754 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -677,21 +677,39 @@ public final class AudioPlaybackConfiguration implements Parcelable { @Override public String toString() { - return "AudioPlaybackConfiguration piid:" + mPlayerIId - + " deviceId:" + mDeviceId - + " type:" + toLogFriendlyPlayerType(mPlayerType) - + " u/pid:" + mClientUid + "/" + mClientPid - + " state:" + toLogFriendlyPlayerState(mPlayerState) - + " attr:" + mPlayerAttr - + " sessionId:" + mSessionId - + " mutedState:" - + " muteFromMasterMute=" + ((mMutedState & PLAYER_MUTE_MASTER) != 0) - + " muteFromStreamVolume=" + ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) - + " muteFromStreamMuted=" + ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) - + " muteFromPlaybackRestricted=" + ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) - != 0) - + " muteFromClientVolume=" + ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) - + " muteFromVolumeShaper=" + ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0); + StringBuilder apcToString = new StringBuilder(); + apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append( + " deviceId:").append(mDeviceId).append(" type:").append( + toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append(mClientUid).append( + "/").append(mClientPid).append(" state:").append( + toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append( + " sessionId:").append(mSessionId).append(" mutedState:"); + if (mMutedState == PLAYER_MUTE_INVALID) { + apcToString.append("invalid "); + } else if (mMutedState == 0) { + apcToString.append("none "); + } else { + if ((mMutedState & PLAYER_MUTE_MASTER) != 0) { + apcToString.append("master "); + } + if ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) { + apcToString.append("streamVolume "); + } + if ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) { + apcToString.append("streamMute "); + } + if ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) { + apcToString.append("playbackRestricted "); + } + if ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) { + apcToString.append("clientVolume "); + } + if ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0) { + apcToString.append("volumeShaper "); + } + } + + return apcToString.toString(); } //===================================================================== diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 63173207ec17..aeb81c12ad6d 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -696,6 +696,13 @@ public class MediaPlayer extends PlayerBase baseRegisterPlayer(sessionId); } + private Parcel createPlayerIIdParcel() { + Parcel parcel = newRequest(); + parcel.writeInt(INVOKE_ID_SET_PLAYER_IID); + parcel.writeInt(mPlayerIId); + return parcel; + } + /* * Update the MediaPlayer SurfaceTexture. * Call after setting a new display surface. @@ -712,6 +719,7 @@ public class MediaPlayer extends PlayerBase private static final int INVOKE_ID_DESELECT_TRACK = 5; private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6; private static final int INVOKE_ID_GET_SELECTED_TRACK = 7; + private static final int INVOKE_ID_SET_PLAYER_IID = 8; /** * Create a request parcel which can be routed to the native media @@ -1309,16 +1317,26 @@ public class MediaPlayer extends PlayerBase * @throws IllegalStateException if it is called in an invalid state */ public void prepare() throws IOException, IllegalStateException { - _prepare(); + Parcel piidParcel = createPlayerIIdParcel(); + try { + int retCode = _prepare(piidParcel); + if (retCode != 0) { + Log.w(TAG, "prepare(): could not set piid " + mPlayerIId); + } + } finally { + piidParcel.recycle(); + } scanInternalSubtitleTracks(); // DrmInfo, if any, has been resolved by now. synchronized (mDrmLock) { mDrmInfoResolved = true; } + } - private native void _prepare() throws IOException, IllegalStateException; + /** Returns the result of sending the {@code piidParcel} to the MediaPlayerService. */ + private native int _prepare(Parcel piidParcel) throws IOException, IllegalStateException; /** * Prepares the player for playback, asynchronously. @@ -1330,7 +1348,20 @@ public class MediaPlayer extends PlayerBase * * @throws IllegalStateException if it is called in an invalid state */ - public native void prepareAsync() throws IllegalStateException; + public void prepareAsync() throws IllegalStateException { + Parcel piidParcel = createPlayerIIdParcel(); + try { + int retCode = _prepareAsync(piidParcel); + if (retCode != 0) { + Log.w(TAG, "prepareAsync(): could not set piid " + mPlayerIId); + } + } finally { + piidParcel.recycle(); + } + } + + /** Returns the result of sending the {@code piidParcel} to the MediaPlayerService. */ + private native int _prepareAsync(Parcel piidParcel) throws IllegalStateException; /** * Starts or resumes playback. If playback had previously been paused, diff --git a/media/java/android/media/projection/MediaProjectionGlobal.java b/media/java/android/media/projection/MediaProjectionGlobal.java new file mode 100644 index 000000000000..4374a052dad2 --- /dev/null +++ b/media/java/android/media/projection/MediaProjectionGlobal.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.projection; + +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.pm.IPackageManager; +import android.hardware.display.DisplayManagerGlobal; +import android.hardware.display.IDisplayManager; +import android.hardware.display.VirtualDisplay; +import android.hardware.display.VirtualDisplayConfig; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.view.Surface; + +/** + * This is a helper for MediaProjection when requests are made from outside an application. This + * should only be used by processes running as shell as a way to capture recordings without being + * an application. The requests will fail if coming from any process that's not Shell. + * @hide + */ +@SystemApi +public class MediaProjectionGlobal { + private static final Object sLock = new Object(); + private static MediaProjectionGlobal sInstance; + + /** + * @return The instance of {@link MediaProjectionGlobal} + */ + @NonNull + public static MediaProjectionGlobal getInstance() { + synchronized (sLock) { + if (sInstance == null) { + final IBinder displayBinder = ServiceManager.getService(Context.DISPLAY_SERVICE); + final IBinder packageBinder = ServiceManager.getService("package"); + if (displayBinder != null && packageBinder != null) { + sInstance = new MediaProjectionGlobal( + IDisplayManager.Stub.asInterface(displayBinder), + IPackageManager.Stub.asInterface(packageBinder)); + } + } + return sInstance; + } + } + + private final IDisplayManager mDm; + private final IPackageManager mPackageManager; + + private MediaProjectionGlobal(IDisplayManager dm, IPackageManager packageManager) { + mDm = dm; + mPackageManager = packageManager; + } + + /** + * Creates a VirtualDisplay that will mirror the content of displayIdToMirror + * @param name The name for the virtual display + * @param width The initial width for the virtual display + * @param height The initial height for the virtual display + * @param displayIdToMirror The displayId that will be mirrored into the virtual display. + * @return VirtualDisplay that can be used to update properties. + */ + @Nullable + public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, + int displayIdToMirror, @Nullable Surface surface) { + + // Density doesn't matter since this virtual display is only used for mirroring. + VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, 1 /* densityDpi */) + .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) + .setDisplayIdToMirror(displayIdToMirror); + if (surface != null) { + builder.setSurface(surface); + } + VirtualDisplayConfig virtualDisplayConfig = builder.build(); + + String[] packages; + try { + packages = mPackageManager.getPackagesForUid(Process.myUid()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + + // Just use the first one since it just needs to match the package when looking it up by + // calling UID in system server. + // The call may come from a rooted device, in that case the requesting uid will be root so + // it will not have any package name + String packageName = packages == null ? null : packages[0]; + DisplayManagerGlobal.VirtualDisplayCallback + callbackWrapper = new DisplayManagerGlobal.VirtualDisplayCallback(null, null); + int displayId; + try { + displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, null, + packageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + return DisplayManagerGlobal.getInstance().createVirtualDisplayWrapper(virtualDisplayConfig, + null, callbackWrapper, displayId); + } +} diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp index 6c6fccb59216..fbc4bb9f9a81 100644 --- a/media/jni/android_media_ImageWriter.cpp +++ b/media/jni/android_media_ImageWriter.cpp @@ -1014,7 +1014,7 @@ static void Image_getLockedImage(JNIEnv* env, jobject thiz, LockedImage *image) status_t res = lockImageFromBuffer( buffer, GRALLOC_USAGE_SW_WRITE_OFTEN, noCrop, fenceFd, image); // Clear the fenceFd as it is already consumed by lock call. - Image_setFenceFd(env, thiz, /*fenceFd*/-1); + env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, -1); if (res != OK) { jniThrowExceptionFmt(env, "java/lang/RuntimeException", "lock buffer failed for format 0x%x", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index a548a472fc3a..da920bb63178 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -369,13 +369,13 @@ android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsu setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */); } -static void -android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) +static jint +android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz, jobject piidParcel) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { + if (mp == nullptr) { jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; + return UNKNOWN_ERROR; } // Handle the case where the display surface was set before the mp was @@ -384,15 +384,20 @@ android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) mp->setVideoSurfaceTexture(st); process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." ); + + // update the piid + Parcel *request = parcelForJavaObject(env, piidParcel); + auto reply = std::make_unique<Parcel>(); + return static_cast<jint>(mp->invoke(*request, reply.get())); } -static void -android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz) +static jint +android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz, jobject piidParcel) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { + if (mp == nullptr) { jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; + return UNKNOWN_ERROR; } // Handle the case where the display surface was set before the mp was @@ -401,6 +406,11 @@ android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz) mp->setVideoSurfaceTexture(st); process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." ); + + // update the piid + Parcel *request = parcelForJavaObject(env, piidParcel); + auto reply = std::make_unique<Parcel>(); + return static_cast<jint>(mp->invoke(*request, reply.get())); } static void @@ -1380,8 +1390,8 @@ static const JNINativeMethod gMethods[] = { {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, {"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback }, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface}, - {"_prepare", "()V", (void *)android_media_MediaPlayer_prepare}, - {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, + {"_prepare", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_prepare}, + {"_prepareAsync", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_prepareAsync}, {"_start", "()V", (void *)android_media_MediaPlayer_start}, {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index 2ddfacf3884a..8b5b726fd2db 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -28,7 +28,7 @@ cc_library_shared { "libaudioclient", "libaudioutils", "libaudiofoundation", - "libbinder" + "libbinder", ], export_shared_lib_headers: [ @@ -42,6 +42,7 @@ cc_library_shared { "-Werror", "-Wunused", "-Wunreachable-code", + "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], // Workaround Clang LTO crash. diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp index d0f1ec6064c1..09c45ea97e9d 100644 --- a/media/jni/audioeffect/Visualizer.cpp +++ b/media/jni/audioeffect/Visualizer.cpp @@ -142,7 +142,8 @@ status_t Visualizer::setCaptureCallBack(capture_cbk_t cbk, void* user, uint32_t mCaptureRate = rate; if (cbk != NULL) { - mCaptureThread = new CaptureThread(this, rate, ((flags & CAPTURE_CALL_JAVA) != 0)); + mCaptureThread = sp<CaptureThread>::make( + sp<Visualizer>::fromExisting(this), rate, ((flags & CAPTURE_CALL_JAVA) != 0)); } ALOGV("setCaptureCallBack() rate: %d thread %p flags 0x%08x", rate, mCaptureThread.get(), mCaptureFlags); @@ -439,7 +440,7 @@ void Visualizer::controlStatusChanged(bool controlGranted) { //------------------------------------------------------------------------- -Visualizer::CaptureThread::CaptureThread(Visualizer* receiver, uint32_t captureRate, +Visualizer::CaptureThread::CaptureThread(const sp<Visualizer>& receiver, uint32_t captureRate, bool bCanCallJava) : Thread(bCanCallJava), mReceiver(receiver) { diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h index 3d5d74a99d0e..b38c01f62cf1 100644 --- a/media/jni/audioeffect/Visualizer.h +++ b/media/jni/audioeffect/Visualizer.h @@ -157,7 +157,8 @@ private: class CaptureThread : public Thread { public: - CaptureThread(Visualizer* visualizer, uint32_t captureRate, bool bCanCallJava = false); + CaptureThread(const sp<Visualizer>& visualizer, + uint32_t captureRate, bool bCanCallJava = false); private: friend class Visualizer; diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 2fb85a7f4b92..63e48aa622d0 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -205,15 +205,15 @@ static sp<AudioEffect> getAudioEffect(JNIEnv* env, jobject thiz) Mutex::Autolock l(sLock); AudioEffect* const ae = (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect); - return sp<AudioEffect>(ae); + return sp<AudioEffect>::fromExisting(ae); } static sp<AudioEffect> setAudioEffect(JNIEnv* env, jobject thiz, const sp<AudioEffect>& ae) { Mutex::Autolock l(sLock); - sp<AudioEffect> old = - (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect); + sp<AudioEffect> old = sp<AudioEffect>::fromExisting( + (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect)); if (ae.get()) { ae->incStrong((void*)setAudioEffect); } @@ -347,8 +347,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t // create the native AudioEffect object parcel = parcelForJavaObject(env, jAttributionSource); attributionSource.readFromParcel(parcel); - lpAudioEffect = new AudioEffect(attributionSource); - if (lpAudioEffect == 0) { + lpAudioEffect = sp<AudioEffect>::make(attributionSource); + if (lpAudioEffect == 0) { // FIXME: I don't think this is actually possible. ALOGE("Error creating AudioEffect"); goto setup_failure; } diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index fac32e02cccd..940712232641 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -251,15 +251,15 @@ static sp<Visualizer> getVisualizer(JNIEnv* env, jobject thiz) Mutex::Autolock l(sLock); Visualizer* const v = (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer); - return sp<Visualizer>(v); + return sp<Visualizer>::fromExisting(v); } static sp<Visualizer> setVisualizer(JNIEnv* env, jobject thiz, const sp<Visualizer>& v) { Mutex::Autolock l(sLock); - sp<Visualizer> old = - (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer); + sp<Visualizer> old = sp<Visualizer>::fromExisting( + (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer)); if (v.get()) { v->incStrong((void*)setVisualizer); } diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle index 5dc2aa0d61cb..f8667edb7e8c 100644 --- a/packages/SettingsLib/Spa/build.gradle +++ b/packages/SettingsLib/Spa/build.gradle @@ -16,7 +16,7 @@ buildscript { ext { - spa_min_sdk = 31 + spa_min_sdk = 21 jetpack_compose_version = '1.2.0-alpha04' jetpack_compose_material3_version = '1.0.0-alpha06' } diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index e5bf8ca60576..e583138fd88a 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -20,7 +20,8 @@ <application android:icon="@mipmap/ic_launcher" android:label="@string/app_label" - android:supportsRtl="true"> + android:supportsRtl="true" + android:enableOnBackInvokedCallback="true"> <activity android:name=".MainActivity" android:exported="true"> @@ -34,5 +35,13 @@ android:name=".GalleryDebugActivity" android:exported="true"> </activity> + + <provider + android:name=".GalleryEntryProvider" + android:authorities="com.android.spa.gallery.provider" + android:enabled="true" + android:exported="false"> + </provider> + </application> </manifest> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt new file mode 100644 index 000000000000..3210eb561fd4 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery + +import com.android.settingslib.spa.framework.EntryProvider + +class GalleryEntryProvider : EntryProvider( + SpaEnvironment.EntryRepository, + "com.android.settingslib.spa.gallery/.MainActivity", +) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt index 787d0961fb21..e5d009930edf 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt @@ -30,6 +30,7 @@ import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.preference.PreferencePageProvider import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider object SpaEnvironment { @@ -49,6 +50,7 @@ object SpaEnvironment { SettingsPagerPageProvider, FooterPageProvider, IllustrationPageProvider, + CategoryPageProvider, ), rootPages = listOf( SettingsPage.create(HomePageProvider.name) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index d8ef937b50c2..f840a37fa81b 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -32,6 +32,7 @@ import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider +import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.widget.scaffold.HomeScaffold @@ -48,6 +49,7 @@ object HomePageProvider : SettingsPageProvider { SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt new file mode 100644 index 000000000000..9a525bf5426d --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.ui + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.Category +import com.android.settingslib.spa.widget.ui.CategoryTitle + +private const val TITLE = "Sample Category" + +object CategoryPageProvider : SettingsPageProvider { + override val name = "Spinner" + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name)) + .setIsAllowSearch(true) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + @Composable + override fun Page(arguments: Bundle?) { + CategoryPage() + } +} + +@Composable +private fun CategoryPage() { + RegularScaffold(title = TITLE) { + CategoryTitle("Category A") + Preference(remember { + object : PreferenceModel { + override val title = "Preference 1" + override val summary = stateOf("Summary 1") + } + }) + Preference(remember { + object : PreferenceModel { + override val title = "Preference 2" + override val summary = stateOf("Summary 2") + } + }) + Category("Category B") { + Preference(remember { + object : PreferenceModel { + override val title = "Preference 3" + override val summary = stateOf("Summary 3") + } + }) + Preference(remember { + object : PreferenceModel { + override val title = "Preference 4" + override val summary = stateOf("Summary 4") + } + }) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun SpinnerPagePreview() { + SettingsTheme { + SpinnerPageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle index 104966d9b097..418d6cb477fa 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle +++ b/packages/SettingsLib/Spa/spa/build.gradle @@ -59,6 +59,7 @@ android { } dependencies { + api "androidx.appcompat:appcompat:1.6.0-rc01" api "androidx.compose.material3:material3:$jetpack_compose_material3_version" api "androidx.compose.material:material-icons-extended:$jetpack_compose_version" api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version" diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index ae15da66d68b..e5a1862599b6 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -22,7 +22,10 @@ import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -50,9 +53,6 @@ open class BrowseActivity( @Composable private fun MainContent() { - val destination = - intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPageName() - val navController = rememberNavController() CompositionLocalProvider(navController.localNavController()) { NavHost(navController, ROOT_PAGE_NAME) { @@ -70,13 +70,23 @@ open class BrowseActivity( } } } + } + InitialDestinationNavigator(navController) + } + + @Composable + private fun InitialDestinationNavigator(navController: NavHostController) { + val destinationNavigated = rememberSaveable { mutableStateOf(false) } + if (destinationNavigated.value) return + destinationNavigated.value = true + LaunchedEffect(Unit) { + val destination = + intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPageName() if (destination.isNotEmpty()) { - LaunchedEffect(Unit) { - navController.navigate(destination) { - popUpTo(navController.graph.findStartDestination().id) { - inclusive = true - } + navController.navigate(destination) { + popUpTo(navController.graph.findStartDestination().id) { + inclusive = true } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt index bd5aaa7e1744..8aef2c6aa068 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.framework import android.content.Intent +import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity @@ -32,6 +33,7 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.android.settingslib.spa.R import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION +import com.android.settingslib.spa.framework.EntryProvider.Companion.PAGE_INFO_QUERY import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryRepository import com.android.settingslib.spa.framework.common.SettingsPage @@ -55,21 +57,12 @@ private const val PARAM_NAME_ENTRY_ID = "eid" open class DebugActivity( private val entryRepository: SettingsEntryRepository, private val browseActivityClass: Class<*>, + private val entryProviderAuthorities: String? = null, ) : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.Theme_SpaLib_DayNight) super.onCreate(savedInstanceState) - - val packageName = browseActivityClass.packageName - val className = browseActivityClass.toString().removePrefix("class $packageName") - for (pageWithEntry in entryRepository.getAllPageWithEntry()) { - if (pageWithEntry.page.hasRuntimeParam()) continue - val route = pageWithEntry.page.buildRoute() - Log.d( - "DEBUG ACTIVITY", - "adb shell am start -n $packageName/$className -e $KEY_DESTINATION $route" - ) - } + displayDebugMessage() setContent { SettingsTheme { @@ -78,6 +71,30 @@ open class DebugActivity( } } + private fun displayDebugMessage() { + if (entryProviderAuthorities == null) return + + try { + contentResolver.query( + Uri.parse("content://$entryProviderAuthorities/${PAGE_INFO_QUERY.queryPath}"), + null, null, null + ).use { cursor -> + while (cursor != null && cursor.moveToNext()) { + val route = cursor.getString(PAGE_INFO_QUERY.getIndex(ColumnName.PAGE_ROUTE)) + val entryCount = cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.ENTRY_COUNT)) + val hasRuntimeParam = + cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.HAS_RUNTIME_PARAM)) == 1 + Log.d( + "DEBUG ACTIVITY", "Page Info: $route ($entryCount) " + + (if (hasRuntimeParam) "with" else "no") + "-runtime-params" + ) + } + } + } catch (e: Exception) { + Log.e("DEBUG ACTIVITY", "Provider querying exception:", e) + } + } + @Composable private fun MainContent() { val navController = rememberNavController() @@ -122,7 +139,7 @@ open class DebugActivity( for (pageWithEntry in entryRepository.getAllPageWithEntry()) { Preference(object : PreferenceModel { override val title = - "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})" + "${pageWithEntry.page.name} (${pageWithEntry.entries.size})" override val summary = pageWithEntry.page.formatArguments().toState() override val onClick = navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}") @@ -142,7 +159,7 @@ open class DebugActivity( fun OnePage(arguments: Bundle?) { val id = arguments!!.getInt(PARAM_NAME_PAGE_ID) val pageWithEntry = entryRepository.getPageWithEntry(id)!! - RegularScaffold(title = "Page ${pageWithEntry.page.displayName}") { + RegularScaffold(title = "Page ${pageWithEntry.page.name}") { Text(text = pageWithEntry.page.formatArguments()) Text(text = "Entry size: ${pageWithEntry.entries.size}") Preference(model = object : PreferenceModel { @@ -158,7 +175,7 @@ open class DebugActivity( fun OneEntry(arguments: Bundle?) { val id = arguments!!.getInt(PARAM_NAME_ENTRY_ID) val entry = entryRepository.getEntry(id)!! - RegularScaffold(title = "Entry ${entry.displayName}") { + RegularScaffold(title = "Entry ${entry.displayName()}") { Preference(model = object : PreferenceModel { override val title = "open entry" override val enabled = (!entry.hasRuntimeParam()).toState() @@ -172,9 +189,9 @@ open class DebugActivity( private fun EntryList(entries: Collection<SettingsEntry>) { for (entry in entries) { Preference(object : PreferenceModel { - override val title = entry.displayName + override val title = entry.displayName() override val summary = - "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState() + "${entry.fromPage?.name} -> ${entry.toPage?.name}".toState() override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}") }) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt new file mode 100644 index 000000000000..90ce182cd2bd --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.content.UriMatcher +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.util.Log +import com.android.settingslib.spa.framework.common.SettingsEntryRepository + +/** + * Enum to define all column names in provider. + */ +enum class ColumnName(val id: String) { + PAGE_NAME("pageName"), + PAGE_ROUTE("pageRoute"), + ENTRY_COUNT("entryCount"), + HAS_RUNTIME_PARAM("hasRuntimeParam"), + PAGE_START_ADB("pageStartAdb"), +} + +data class QueryDefinition( + val queryPath: String, + val queryMatchCode: Int, + val columnNames: List<ColumnName>, +) { + fun getColumns(): Array<String> { + return columnNames.map { it.id }.toTypedArray() + } + + fun getIndex(name: ColumnName): Int { + return columnNames.indexOf(name) + } +} + +open class EntryProvider( + private val entryRepository: SettingsEntryRepository, + private val browseActivityComponentName: String? = null, +) : ContentProvider() { + + private var mMatcher: UriMatcher? = null + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { + TODO("Implement this to handle requests to delete one or more rows") + } + + override fun getType(uri: Uri): String? { + TODO( + "Implement this to handle requests for the MIME type of the data" + + "at the given URI" + ) + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + TODO("Implement this to handle requests to insert a new row.") + } + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array<String>? + ): Int { + TODO("Implement this to handle requests to update one or more rows.") + } + + override fun onCreate(): Boolean { + return true + } + + override fun attachInfo(context: Context?, info: ProviderInfo?) { + mMatcher = UriMatcher(UriMatcher.NO_MATCH) + if (info != null) { + mMatcher!!.addURI( + info.authority, + PAGE_START_COMMAND_QUERY.queryPath, + PAGE_START_COMMAND_QUERY.queryMatchCode + ) + mMatcher!!.addURI( + info.authority, + PAGE_INFO_QUERY.queryPath, + PAGE_INFO_QUERY.queryMatchCode + ) + } + super.attachInfo(context, info) + } + + override fun query( + uri: Uri, + projection: Array<String>?, + selection: String?, + selectionArgs: Array<String>?, + sortOrder: String? + ): Cursor? { + return try { + when (mMatcher!!.match(uri)) { + PAGE_START_COMMAND_QUERY.queryMatchCode -> queryPageStartCommand() + PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo() + else -> throw UnsupportedOperationException("Unknown Uri $uri") + } + } catch (e: UnsupportedOperationException) { + throw e + } catch (e: Exception) { + Log.e("EntryProvider", "Provider querying exception:", e) + null + } + } + + private fun queryPageStartCommand(): Cursor { + val componentName = browseActivityComponentName ?: "[component-name]" + val cursor = MatrixCursor(PAGE_START_COMMAND_QUERY.getColumns()) + for (pageWithEntry in entryRepository.getAllPageWithEntry()) { + val page = pageWithEntry.page + if (!page.hasRuntimeParam()) { + cursor.newRow().add( + ColumnName.PAGE_START_ADB.id, + "adb shell am start -n $componentName" + + " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}" + ) + } + } + return cursor + } + + private fun queryPageInfo(): Cursor { + val cursor = MatrixCursor(PAGE_INFO_QUERY.getColumns()) + for (pageWithEntry in entryRepository.getAllPageWithEntry()) { + val page = pageWithEntry.page + cursor.newRow().add(ColumnName.PAGE_NAME.id, page.name) + .add(ColumnName.PAGE_ROUTE.id, page.buildRoute()) + .add(ColumnName.ENTRY_COUNT.id, pageWithEntry.entries.size) + .add(ColumnName.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0) + } + return cursor + } + + companion object { + val PAGE_START_COMMAND_QUERY = QueryDefinition( + "page_start", 1, + listOf(ColumnName.PAGE_START_ADB) + ) + + val PAGE_INFO_QUERY = QueryDefinition( + "page_info", 2, + listOf( + ColumnName.PAGE_NAME, + ColumnName.PAGE_ROUTE, + ColumnName.ENTRY_COUNT, + ColumnName.HAS_RUNTIME_PARAM, + ) + ) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index b0a1cbe8c224..445c4ebfcee2 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -47,10 +47,6 @@ data class SettingsPage( // The name of the page, which is used to compute the unique id, and need to be stable. val name: String, - // The display name of the page, for better readability. - // By default, it is the same as name. - val displayName: String, - // Defined parameters of this page. val parameter: List<NamedNavArgument> = emptyList(), @@ -74,7 +70,7 @@ data class SettingsPage( } fun formatAll(): String { - return "$displayName ${formatArguments()}" + return "$name ${formatArguments()}" } fun buildRoute(highlightEntryName: String? = null): String { @@ -104,10 +100,6 @@ data class SettingsEntry( // The owner page of this entry. val owner: SettingsPage, - // The display name of the entry, for better readability. - // By default, it is $owner:$name - val displayName: String, - // Defines linking of Settings entries val fromPage: SettingsPage? = null, val toPage: SettingsPage? = null, @@ -146,7 +138,7 @@ data class SettingsEntry( val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {}, ) { fun formatAll(): String { - val content = listOf<String>( + val content = listOf( "owner = ${owner.formatAll()}", "linkFrom = ${fromPage?.formatAll()}", "linkTo = ${toPage?.formatAll()}", @@ -154,6 +146,11 @@ data class SettingsEntry( return content.joinToString("\n") } + // The display name of the entry, for better readability. + fun displayName(): String { + return "${owner.name}:$name" + } + private fun getDisplayPage(): SettingsPage { // Display the entry on its from-page, or on its owner page if the from-page is unset. return fromPage ?: owner @@ -185,7 +182,6 @@ class SettingsPageBuilder( private val name: String, private val parameter: List<NamedNavArgument> = emptyList() ) { - private var displayName: String? = null private var arguments: Bundle? = null fun build(): SettingsPage { @@ -193,7 +189,6 @@ class SettingsPageBuilder( return SettingsPage( id = "$name:${normArguments?.toString()}".toUniqueId(), name = name, - displayName = displayName ?: name, parameter = parameter, arguments = arguments, ) @@ -209,7 +204,6 @@ class SettingsPageBuilder( * The helper to build a Settings Entry instance. */ class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) { - private var displayName: String? = null private var fromPage: SettingsPage? = null private var toPage: SettingsPage? = null private var isAllowSearch: Boolean? = null @@ -220,7 +214,6 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings fun build(): SettingsEntry { return SettingsEntry( id = "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toUniqueId(), - displayName = displayName ?: "${owner.displayName}:$name", name = name, owner = owner, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt index 4626f0bbed49..d1a11384ff47 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt @@ -16,9 +16,12 @@ package com.android.settingslib.spa.framework.theme +import android.os.Build import androidx.compose.material3.ColorScheme +import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext @@ -28,8 +31,12 @@ internal fun materialColorScheme(isDarkTheme: Boolean): ColorScheme { val context = LocalContext.current return remember(isDarkTheme) { when { - isDarkTheme -> dynamicDarkColorScheme(context) - else -> dynamicLightColorScheme(context) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (isDarkTheme) dynamicDarkColorScheme(context) + else dynamicLightColorScheme(context) + } + isDarkTheme -> darkColorScheme() + else -> lightColorScheme() } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt index bc316f71cf23..fa17e08e9885 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.framework.theme import android.content.Context +import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf @@ -44,8 +45,12 @@ internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme { val context = LocalContext.current return remember(isDarkTheme) { when { - isDarkTheme -> dynamicDarkColorScheme(context) - else -> dynamicLightColorScheme(context) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (isDarkTheme) dynamicDarkColorScheme(context) + else dynamicLightColorScheme(context) + } + isDarkTheme -> darkColorScheme() + else -> lightColorScheme() } } } @@ -101,3 +106,37 @@ private fun dynamicDarkColorScheme(context: Context): SettingsColorScheme { onSpinnerItemContainer = tonalPalette.neutralVariant30, ) } + +private fun darkColorScheme(): SettingsColorScheme { + val tonalPalette = tonalPalette() + return SettingsColorScheme( + background = tonalPalette.neutral10, + categoryTitle = tonalPalette.primary90, + surface = tonalPalette.neutral20, + surfaceHeader = tonalPalette.neutral30, + secondaryText = tonalPalette.neutralVariant80, + primaryContainer = tonalPalette.secondary90, + onPrimaryContainer = tonalPalette.neutral10, + spinnerHeaderContainer = tonalPalette.primary90, + onSpinnerHeaderContainer = tonalPalette.neutral10, + spinnerItemContainer = tonalPalette.secondary90, + onSpinnerItemContainer = tonalPalette.neutralVariant30, + ) +} + +private fun lightColorScheme(): SettingsColorScheme { + val tonalPalette = tonalPalette() + return SettingsColorScheme( + background = tonalPalette.neutral95, + categoryTitle = tonalPalette.primary40, + surface = tonalPalette.neutral99, + surfaceHeader = tonalPalette.neutral90, + secondaryText = tonalPalette.neutralVariant30, + primaryContainer = tonalPalette.primary90, + onPrimaryContainer = tonalPalette.neutral10, + spinnerHeaderContainer = tonalPalette.primary90, + onSpinnerHeaderContainer = tonalPalette.neutral10, + spinnerItemContainer = tonalPalette.secondary90, + onSpinnerItemContainer = tonalPalette.neutralVariant30, + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt index f81f5e734fb4..c205aae84fd0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt @@ -118,6 +118,84 @@ internal class SettingsTonalPalette( val tertiary0: Color, ) +/** Static colors in Material. */ +internal fun tonalPalette() = SettingsTonalPalette( + // The neutral static tonal range. + neutral100 = Color(red = 255, green = 255, blue = 255), + neutral99 = Color(red = 255, green = 251, blue = 254), + neutral95 = Color(red = 244, green = 239, blue = 244), + neutral90 = Color(red = 230, green = 225, blue = 229), + neutral80 = Color(red = 201, green = 197, blue = 202), + neutral70 = Color(red = 174, green = 170, blue = 174), + neutral60 = Color(red = 147, green = 144, blue = 148), + neutral50 = Color(red = 120, green = 117, blue = 121), + neutral40 = Color(red = 96, green = 93, blue = 98), + neutral30 = Color(red = 72, green = 70, blue = 73), + neutral20 = Color(red = 49, green = 48, blue = 51), + neutral10 = Color(red = 28, green = 27, blue = 31), + neutral0 = Color(red = 0, green = 0, blue = 0), + + // The neutral variant static tonal range, sometimes called "neutral 2". + neutralVariant100 = Color(red = 255, green = 255, blue = 255), + neutralVariant99 = Color(red = 255, green = 251, blue = 254), + neutralVariant95 = Color(red = 245, green = 238, blue = 250), + neutralVariant90 = Color(red = 231, green = 224, blue = 236), + neutralVariant80 = Color(red = 202, green = 196, blue = 208), + neutralVariant70 = Color(red = 174, green = 169, blue = 180), + neutralVariant60 = Color(red = 147, green = 143, blue = 153), + neutralVariant50 = Color(red = 121, green = 116, blue = 126), + neutralVariant40 = Color(red = 96, green = 93, blue = 102), + neutralVariant30 = Color(red = 73, green = 69, blue = 79), + neutralVariant20 = Color(red = 50, green = 47, blue = 55), + neutralVariant10 = Color(red = 29, green = 26, blue = 34), + neutralVariant0 = Color(red = 0, green = 0, blue = 0), + + // The primary static tonal range. + primary100 = Color(red = 255, green = 255, blue = 255), + primary99 = Color(red = 255, green = 251, blue = 254), + primary95 = Color(red = 246, green = 237, blue = 255), + primary90 = Color(red = 234, green = 221, blue = 255), + primary80 = Color(red = 208, green = 188, blue = 255), + primary70 = Color(red = 182, green = 157, blue = 248), + primary60 = Color(red = 154, green = 130, blue = 219), + primary50 = Color(red = 127, green = 103, blue = 190), + primary40 = Color(red = 103, green = 80, blue = 164), + primary30 = Color(red = 79, green = 55, blue = 139), + primary20 = Color(red = 56, green = 30, blue = 114), + primary10 = Color(red = 33, green = 0, blue = 93), + primary0 = Color(red = 33, green = 0, blue = 93), + + // The secondary static tonal range. + secondary100 = Color(red = 255, green = 255, blue = 255), + secondary99 = Color(red = 255, green = 251, blue = 254), + secondary95 = Color(red = 246, green = 237, blue = 255), + secondary90 = Color(red = 232, green = 222, blue = 248), + secondary80 = Color(red = 204, green = 194, blue = 220), + secondary70 = Color(red = 176, green = 167, blue = 192), + secondary60 = Color(red = 149, green = 141, blue = 165), + secondary50 = Color(red = 122, green = 114, blue = 137), + secondary40 = Color(red = 98, green = 91, blue = 113), + secondary30 = Color(red = 74, green = 68, blue = 88), + secondary20 = Color(red = 51, green = 45, blue = 65), + secondary10 = Color(red = 29, green = 25, blue = 43), + secondary0 = Color(red = 0, green = 0, blue = 0), + + // The tertiary static tonal range. + tertiary100 = Color(red = 255, green = 255, blue = 255), + tertiary99 = Color(red = 255, green = 251, blue = 250), + tertiary95 = Color(red = 255, green = 236, blue = 241), + tertiary90 = Color(red = 255, green = 216, blue = 228), + tertiary80 = Color(red = 239, green = 184, blue = 200), + tertiary70 = Color(red = 210, green = 157, blue = 172), + tertiary60 = Color(red = 181, green = 131, blue = 146), + tertiary50 = Color(red = 152, green = 105, blue = 119), + tertiary40 = Color(red = 125, green = 82, blue = 96), + tertiary30 = Color(red = 99, green = 59, blue = 72), + tertiary20 = Color(red = 73, green = 37, blue = 50), + tertiary10 = Color(red = 49, green = 17, blue = 29), + tertiary0 = Color(red = 0, green = 0, blue = 0), +) + /** Dynamic colors in Material. */ internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette( // The neutral tonal range from the generated dynamic color palette. diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt index aaf8107b723d..d7d7750e37b9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt @@ -25,9 +25,12 @@ fun List<NamedNavArgument>.navRoute(): String { } fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String { - if (arguments == null) return "" val argsArray = mutableListOf<String>() for (navArg in this) { + if (arguments == null || !arguments.containsKey(navArg.name)) { + argsArray.add("[rt]") + continue + } when (navArg.argument.type) { NavType.StringType -> { argsArray.add(arguments.getString(navArg.name, "")) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt new file mode 100644 index 000000000000..6aac5bf3839a --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme + +/** + * A category title that is placed before a group of similar items. + */ +@Composable +fun CategoryTitle(title: String) { + Text( + text = title, + modifier = Modifier.padding( + start = SettingsDimension.itemPaddingStart, + top = 20.dp, + end = SettingsDimension.itemPaddingEnd, + bottom = 8.dp, + ), + color = SettingsTheme.colorScheme.categoryTitle, + style = MaterialTheme.typography.labelMedium, + ) +} + +/** + * A container that is used to group similar items. A [Category] displays a [CategoryTitle] and + * visually separates groups of items. + */ +@Composable +fun Category(title: String, content: @Composable ColumnScope.() -> Unit) { + Column { + var displayTitle by remember { mutableStateOf(false) } + if (displayTitle) CategoryTitle(title = title) + Column( + modifier = Modifier.onGloballyPositioned { coordinates -> + displayTitle = coordinates.size.height > 0 + }, + content = content, + ) + } +} + +@Preview +@Composable +private fun CategoryPreview() { + SettingsTheme { + CategoryTitle("Appearance") + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt new file mode 100644 index 000000000000..16e09ee854d8 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.ui + +import androidx.compose.runtime.remember +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CategoryTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun categoryTitle() { + composeTestRule.setContent { + CategoryTitle(title = "CategoryTitle") + } + + composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed() + } + + @Test + fun category_hasContent_titleDisplayed() { + composeTestRule.setContent { + Category(title = "CategoryTitle") { + Preference(remember { + object : PreferenceModel { + override val title = "Some Preference" + override val summary = stateOf("Some summary") + } + }) + } + } + + composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed() + } + + @Test + fun category_noContent_titleNotDisplayed() { + composeTestRule.setContent { + Category(title = "CategoryTitle") {} + } + + composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist() + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt new file mode 100644 index 000000000000..061580702327 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.enterprise + +import android.app.admin.DevicePolicyResources.Strings.Settings +import android.content.Context +import android.os.UserHandle +import android.os.UserManager +import androidx.lifecycle.LiveData +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.settingslib.RestrictedLockUtilsInternal +import com.android.settingslib.spaprivileged.R + +data class Restrictions( + val userId: Int, + val keys: List<String>, +) + +sealed class RestrictedMode + +object NoRestricted : RestrictedMode() + +object BaseUserRestricted : RestrictedMode() + +data class BlockedByAdmin( + val enterpriseRepository: EnterpriseRepository, + val enforcedAdmin: EnforcedAdmin, +) : RestrictedMode() { + fun getSummary(checked: Boolean?): String = when (checked) { + true -> enterpriseRepository.getEnterpriseString( + Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin + ) + false -> enterpriseRepository.getEnterpriseString( + Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.disabled_by_admin + ) + else -> "" + } +} + +class RestrictionsProvider( + private val context: Context, + private val restrictions: Restrictions, +) { + private val userManager by lazy { UserManager.get(context) } + private val enterpriseRepository by lazy { EnterpriseRepository(context) } + + val restrictedMode = object : LiveData<RestrictedMode>() { + override fun onActive() { + postValue(getRestrictedMode()) + } + + override fun onInactive() { + } + } + + private fun getRestrictedMode(): RestrictedMode { + for (key in restrictions.keys) { + if (userManager.hasBaseUserRestriction(key, UserHandle.of(restrictions.userId))) { + return BaseUserRestricted + } + } + for (key in restrictions.keys) { + RestrictedLockUtilsInternal + .checkIfRestrictionEnforced(context, key, restrictions.userId) + ?.let { + return BlockedByAdmin( + enterpriseRepository = enterpriseRepository, + enforcedAdmin = it, + ) + } + } + return NoRestricted + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index c031fe808346..ae4f8bd1c597 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -34,11 +34,12 @@ import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator -import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.model.app.toRoute +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference import kotlinx.coroutines.Dispatchers private const val ENTRY_NAME = "AllowControl" @@ -105,7 +106,7 @@ private fun TogglePermissionAppInfoPage( LaunchedEffect(model, Dispatchers.Default) { model.initState() } - SwitchPreference(model) + RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys)) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index 4c748b8d0fc8..74a50de1da92 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -37,6 +37,8 @@ interface TogglePermissionAppListModel<T : AppRecord> { val pageTitleResId: Int val switchTitleResId: Int val footerResId: Int + val switchRestrictionKeys: List<String> + get() = emptyList() /** * Loads the extra info for the App List, and generates the [AppRecord] List. diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 65cc4f9a165c..475e93053e5a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -22,6 +22,7 @@ import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -37,6 +38,10 @@ import com.android.settingslib.spa.framework.util.getStringArg import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.userId +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider +import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference import kotlinx.coroutines.flow.Flow private const val ENTRY_NAME = "AppList" @@ -127,15 +132,32 @@ private class TogglePermissionInternalAppListModel<T : AppRecord>( @Composable override fun getSummary(option: Int, record: T): State<String> { + val restrictionsProvider = remember { + val restrictions = Restrictions( + userId = record.app.userId, + keys = listModel.switchRestrictionKeys, + ) + RestrictionsProvider(context, restrictions) + } + val restrictedMode = restrictionsProvider.restrictedMode.observeAsState() val allowed = listModel.isAllowed(record) return remember { derivedStateOf { - when (allowed.value) { - true -> context.getString(R.string.app_permission_summary_allowed) - false -> context.getString(R.string.app_permission_summary_not_allowed) - else -> "" - } + RestrictedSwitchPreference.getSummary( + context = context, + restrictedMode = restrictedMode.value, + noRestrictedSummary = getNoRestrictedSummary(allowed), + checked = allowed, + ).value } } } + + private fun getNoRestrictedSummary(allowed: State<Boolean?>) = derivedStateOf { + when (allowed.value) { + true -> context.getString(R.string.app_permission_summary_allowed) + false -> context.getString(R.string.app_permission_summary_not_allowed) + else -> context.getString(R.string.summary_placeholder) + } + } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt new file mode 100644 index 000000000000..31fd3ad6d521 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.template.preference + +import android.content.Context +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role +import com.android.settingslib.RestrictedLockUtils +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.widget.preference.SwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider + +@Composable +fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) { + if (restrictions.keys.isEmpty()) { + SwitchPreference(model) + return + } + val context = LocalContext.current + val restrictionsProvider = remember { RestrictionsProvider(context, restrictions) } + val restrictedMode = restrictionsProvider.restrictedMode.observeAsState().value ?: return + val restrictedSwitchModel = remember(restrictedMode) { + RestrictedSwitchPreferenceModel(context, model, restrictedMode) + } + Box(remember { restrictedSwitchModel.getModifier() }) { + SwitchPreference(restrictedSwitchModel) + } +} + +object RestrictedSwitchPreference { + fun getSummary( + context: Context, + restrictedMode: RestrictedMode?, + noRestrictedSummary: State<String>, + checked: State<Boolean?>, + ): State<String> = when (restrictedMode) { + is NoRestricted -> noRestrictedSummary + is BaseUserRestricted -> stateOf(context.getString(R.string.disabled)) + is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) } + null -> stateOf(context.getString(R.string.summary_placeholder)) + } +} + +private class RestrictedSwitchPreferenceModel( + private val context: Context, + model: SwitchPreferenceModel, + private val restrictedMode: RestrictedMode, +) : SwitchPreferenceModel { + override val title = model.title + + override val summary = RestrictedSwitchPreference.getSummary( + context = context, + restrictedMode = restrictedMode, + noRestrictedSummary = model.summary, + checked = model.checked, + ) + + override val checked = when (restrictedMode) { + is NoRestricted -> model.checked + is BaseUserRestricted -> stateOf(false) + is BlockedByAdmin -> model.checked + } + + override val changeable = when (restrictedMode) { + is NoRestricted -> model.changeable + is BaseUserRestricted -> stateOf(false) + is BlockedByAdmin -> stateOf(false) + } + + override val onCheckedChange = when (restrictedMode) { + is NoRestricted -> model.onCheckedChange + is BaseUserRestricted -> null + is BlockedByAdmin -> null + } + + fun getModifier(): Modifier = when (restrictedMode) { + is BlockedByAdmin -> Modifier.clickable(role = Role.Switch) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent( + context, restrictedMode.enforcedAdmin + ) + } + else -> Modifier + } +} diff --git a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml deleted file mode 100644 index c8a80ac57c00..000000000000 --- a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.24" android:color="?android:attr/colorBackground" /> -</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml deleted file mode 100644 index 8dcfdbb8cf1e..000000000000 --- a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.47" android:color="?android:attr/colorBackground" /> -</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml deleted file mode 100644 index 34de5489a28b..000000000000 --- a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.3" android:color="?android:attr/colorForeground" /> -</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml deleted file mode 100644 index 15944c3a2a07..000000000000 --- a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?android:attr/colorForeground" /> -</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/drawable/ic_5g_uc_mobiledata.xml b/packages/SettingsLib/res/drawable/ic_5g_uc_mobiledata.xml deleted file mode 100644 index 93fcad298c3f..000000000000 --- a/packages/SettingsLib/res/drawable/ic_5g_uc_mobiledata.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="27dp" - android:height="16dp" - android:viewportWidth="27.0" - android:viewportHeight="16.0"> - <path - android:fillColor="#FF000000" - android:pathData="M5.3,13.22c-0.57,0 -1.1,-0.11 -1.58,-0.34c-0.48,-0.22 -0.87,-0.55 -1.18,-0.98C2.24,11.47 2.06,10.93 2,10.3l1.48,-0.2c0.07,0.5 0.25,0.92 0.56,1.25c0.32,0.32 0.74,0.48 1.26,0.48c0.57,0 1.02,-0.18 1.34,-0.55c0.33,-0.37 0.49,-0.87 0.49,-1.48c0,-0.61 -0.16,-1.09 -0.49,-1.46C6.32,7.96 5.88,7.78 5.32,7.78C5,7.78 4.7,7.85 4.42,8C4.15,8.14 3.93,8.33 3.76,8.56L2.28,7.92l0.6,-4.94h5.26v1.36H4.1L3.72,7.02l0.08,0.03C4,6.87 4.25,6.73 4.55,6.62c0.3,-0.12 0.63,-0.18 1.01,-0.18c0.6,0 1.13,0.14 1.6,0.41C7.62,7.11 7.98,7.5 8.24,8c0.27,0.5 0.41,1.1 0.41,1.79c0,0.66 -0.14,1.26 -0.43,1.78c-0.28,0.51 -0.67,0.92 -1.18,1.22C6.55,13.08 5.97,13.22 5.3,13.22z"/> - <path - android:fillColor="#FF000000" - android:pathData="M14.51,13.22c-0.88,0 -1.66,-0.21 -2.34,-0.64c-0.67,-0.44 -1.2,-1.05 -1.6,-1.83C10.19,9.95 10,9.03 10,7.99c0,-1.05 0.21,-1.96 0.62,-2.74c0.41,-0.79 0.97,-1.4 1.67,-1.83c0.7,-0.44 1.5,-0.66 2.39,-0.66c1.09,0 2.01,0.25 2.74,0.76c0.75,0.5 1.22,1.21 1.41,2.11l-1.47,0.39c-0.17,-0.56 -0.48,-1 -0.94,-1.32c-0.45,-0.33 -1.03,-0.49 -1.75,-0.49c-0.57,0 -1.09,0.14 -1.57,0.43c-0.48,0.29 -0.85,0.71 -1.13,1.27s-0.42,1.25 -0.42,2.07c0,0.81 0.14,1.5 0.41,2.07c0.28,0.56 0.65,0.98 1.11,1.27c0.47,0.29 0.98,0.43 1.54,0.43c0.57,0 1.06,-0.11 1.47,-0.34c0.42,-0.23 0.75,-0.55 0.99,-0.94c0.25,-0.4 0.41,-0.85 0.46,-1.36h-2.88V7.79h4.37c0,0.87 0,1.74 0,2.6c0,0.87 0,1.74 0,2.6H17.6v-1.4h-0.08c-0.28,0.49 -0.67,0.88 -1.18,1.18C15.85,13.07 15.24,13.22 14.51,13.22z"/> - <path - android:fillColor="#FF000000" - android:pathData="M23,7.39c-0.53,0 -0.94,-0.16 -1.25,-0.47C21.45,6.6 21.3,6.16 21.3,5.6V3h0.8v2.66c0,0.3 0.08,0.54 0.23,0.71C22.48,6.54 22.7,6.62 23,6.62c0.3,0 0.52,-0.08 0.67,-0.25c0.15,-0.17 0.23,-0.41 0.23,-0.71V3h0.8v2.6c0,0.36 -0.07,0.67 -0.2,0.94s-0.33,0.48 -0.58,0.62C23.65,7.32 23.35,7.39 23,7.39z"/> - <path - android:fillColor="#FF000000" - android:pathData="M22.99,13.1c-0.39,0 -0.73,-0.09 -1.03,-0.28c-0.3,-0.19 -0.53,-0.45 -0.7,-0.79C21.08,11.7 21,11.3 21,10.85c0,-0.46 0.08,-0.85 0.25,-1.19c0.17,-0.34 0.41,-0.6 0.71,-0.78c0.3,-0.18 0.65,-0.28 1.04,-0.28c0.31,0 0.59,0.05 0.86,0.16c0.26,0.11 0.48,0.27 0.65,0.48c0.18,0.21 0.28,0.48 0.32,0.8l-0.83,0.14c-0.06,-0.26 -0.17,-0.46 -0.35,-0.6C23.49,9.44 23.27,9.37 23,9.37c-0.22,0 -0.42,0.06 -0.61,0.17c-0.18,0.11 -0.32,0.28 -0.43,0.5c-0.1,0.22 -0.16,0.49 -0.16,0.81c0,0.32 0.05,0.58 0.16,0.8s0.25,0.39 0.43,0.5c0.18,0.11 0.38,0.17 0.61,0.17c0.26,0 0.47,-0.07 0.65,-0.21c0.18,-0.14 0.3,-0.34 0.35,-0.59l0.85,0.09c-0.06,0.29 -0.17,0.54 -0.32,0.77c-0.15,0.22 -0.36,0.39 -0.61,0.52C23.66,13.03 23.35,13.1 22.99,13.1z"/> -</vector> diff --git a/packages/SettingsLib/res/drawable/ic_5g_uw_mobiledata.xml b/packages/SettingsLib/res/drawable/ic_5g_uw_mobiledata.xml deleted file mode 100644 index ca47b6ff2edb..000000000000 --- a/packages/SettingsLib/res/drawable/ic_5g_uw_mobiledata.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="27dp" - android:height="16dp" - android:viewportWidth="27.0" - android:viewportHeight="16.0"> - <path - android:fillColor="#FF000000" - android:pathData="M5.3,13.22c-0.57,0 -1.1,-0.11 -1.58,-0.34c-0.48,-0.22 -0.87,-0.55 -1.18,-0.98C2.24,11.47 2.06,10.93 2,10.3l1.48,-0.2c0.07,0.5 0.25,0.92 0.56,1.25c0.32,0.32 0.74,0.48 1.26,0.48c0.57,0 1.02,-0.18 1.34,-0.55c0.33,-0.37 0.49,-0.87 0.49,-1.48c0,-0.61 -0.16,-1.09 -0.49,-1.46C6.32,7.96 5.88,7.78 5.32,7.78C5,7.78 4.7,7.85 4.42,8C4.15,8.14 3.93,8.33 3.76,8.56L2.28,7.92l0.6,-4.94h5.26v1.36H4.1L3.72,7.02l0.08,0.03C4,6.87 4.25,6.73 4.55,6.62c0.3,-0.12 0.63,-0.18 1.01,-0.18c0.6,0 1.13,0.14 1.6,0.41C7.62,7.11 7.98,7.5 8.24,8c0.27,0.5 0.41,1.1 0.41,1.79c0,0.66 -0.14,1.26 -0.43,1.78c-0.28,0.51 -0.67,0.92 -1.18,1.22C6.55,13.08 5.97,13.22 5.3,13.22z"/> - <path - android:fillColor="#FF000000" - android:pathData="M14.51,13.22c-0.88,0 -1.66,-0.21 -2.34,-0.64c-0.67,-0.44 -1.2,-1.05 -1.6,-1.83C10.19,9.95 10,9.03 10,7.99c0,-1.05 0.21,-1.96 0.62,-2.74c0.41,-0.79 0.97,-1.4 1.67,-1.83c0.7,-0.44 1.5,-0.66 2.39,-0.66c1.09,0 2.01,0.25 2.74,0.76c0.75,0.5 1.22,1.21 1.41,2.11l-1.47,0.39c-0.17,-0.56 -0.48,-1 -0.94,-1.32c-0.45,-0.33 -1.03,-0.49 -1.75,-0.49c-0.57,0 -1.09,0.14 -1.57,0.43c-0.48,0.29 -0.85,0.71 -1.13,1.27s-0.42,1.25 -0.42,2.07c0,0.81 0.14,1.5 0.41,2.07c0.28,0.56 0.65,0.98 1.11,1.27c0.47,0.29 0.98,0.43 1.54,0.43c0.57,0 1.06,-0.11 1.47,-0.34c0.42,-0.23 0.75,-0.55 0.99,-0.94c0.25,-0.4 0.41,-0.85 0.46,-1.36h-2.88V7.79h4.37c0,0.87 0,1.74 0,2.6c0,0.87 0,1.74 0,2.6H17.6v-1.4h-0.08c-0.28,0.49 -0.67,0.88 -1.18,1.18C15.85,13.07 15.24,13.22 14.51,13.22z"/> - <path - android:fillColor="#FF000000" - android:pathData="M23,7.39c-0.53,0 -0.94,-0.16 -1.25,-0.47C21.45,6.6 21.3,6.16 21.3,5.6V3h0.8v2.66c0,0.3 0.08,0.54 0.23,0.71C22.48,6.54 22.7,6.62 23,6.62c0.3,0 0.52,-0.08 0.67,-0.25c0.15,-0.17 0.23,-0.41 0.23,-0.71V3h0.8v2.6c0,0.36 -0.07,0.67 -0.2,0.94s-0.33,0.48 -0.58,0.62C23.65,7.32 23.35,7.39 23,7.39z"/> - <path - android:fillColor="#FF000000" - android:pathData="M21.41,13L20.3,8.7h0.73l0.64,2.78l0.07,0.38h0.04l0.09,-0.38l0.81,-2.78h0.66l0.79,2.78l0.09,0.37h0.04l0.07,-0.37l0.65,-2.78h0.72L24.59,13H23.9l-0.78,-2.84l-0.1,-0.41h-0.04l-0.1,0.41L22.08,13H21.41z"/> -</vector> diff --git a/packages/SettingsLib/res/layout/preference_category_divider.xml b/packages/SettingsLib/res/layout/preference_category_divider.xml deleted file mode 100644 index 6644eecd71fa..000000000000 --- a/packages/SettingsLib/res/layout/preference_category_divider.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/two_target_divider" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:gravity="start|center_vertical" - android:orientation="horizontal"> - <View - android:layout_height="1dp" - android:layout_width="match_parent" - android:background="?android:attr/dividerHorizontal" /> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/res/layout/preference_category_material_settings_with_divider.xml b/packages/SettingsLib/res/layout/preference_category_material_settings_with_divider.xml deleted file mode 100644 index 5b5d474b13f9..000000000000 --- a/packages/SettingsLib/res/layout/preference_category_material_settings_with_divider.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> - -<!-- Similar to preference_category_material_settings.xml, except that this always adds - a divider above category. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <include layout="@layout/preference_category_divider"/> - <include layout="@layout/preference_category_material"/> -</LinearLayout> diff --git a/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml b/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml deleted file mode 100644 index 52d77758051f..000000000000 --- a/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2016, 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. -*/ ---> - -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:paddingEnd="16dp" - android:paddingStart="16dp"> - - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceListItemSmall" - android:textColor="?android:attr/textColorAlertDialogListItem" /> - -</RelativeLayout> diff --git a/packages/SettingsLib/res/layout/settings_dialog_title.xml b/packages/SettingsLib/res/layout/settings_dialog_title.xml deleted file mode 100644 index 1e065e0dbb4d..000000000000 --- a/packages/SettingsLib/res/layout/settings_dialog_title.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/settings_title_panel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/settings_title_template" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="center" - android:paddingStart="?android:attr/dialogPreferredPadding" - android:paddingEnd="?android:attr/dialogPreferredPadding" - android:paddingTop="@*android:dimen/dialog_padding_top_material"> - - <ImageView - android:id="@+id/settings_icon" - android:layout_width="24dip" - android:layout_height="24dip" - android:layout_marginBottom="12dip" /> - - <TextView - android:id="@+id/settings_title" - android:singleLine="true" - android:ellipsize="end" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAlignment="center" - style="?android:attr/windowTitleStyle" /> - </LinearLayout> - - <Space - android:id="@+id/settings_title_divider" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="@*android:dimen/dialog_title_divider_material" /> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index 663e8e4b14d8..179573b4b9c7 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -158,18 +158,6 @@ <item>Opus</item> </string-array> - <!-- Values for Bluetooth Audio Codec selection preference. --> - <string-array name="bluetooth_a2dp_codec_values" translatable="false" > - <item>1000000</item> - <item>0</item> - <item>1</item> - <item>2</item> - <item>3</item> - <item>4</item> - <item>5</item> - <item>6</item> - </string-array> - <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]--> <string-array name="bluetooth_a2dp_codec_summaries" > <item>Use System Selection (Default)</item> @@ -191,15 +179,6 @@ <item>96.0 kHz</item> </string-array> - <!-- Values for Bluetooth Audio Codec Sample Rate selection preference. --> - <string-array name="bluetooth_a2dp_codec_sample_rate_values" translatable="false" > - <item>0</item> - <item>1</item> - <item>2</item> - <item>4</item> - <item>8</item> - </string-array> - <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]--> <string-array name="bluetooth_a2dp_codec_sample_rate_summaries" > <item>Use System Selection (Default)</item> @@ -217,14 +196,6 @@ <item>32 bits/sample</item> </string-array> - <!-- Values for Bluetooth Audio Codec Bits Per Sample selection preference. --> - <string-array name="bluetooth_a2dp_codec_bits_per_sample_values" translatable="false" > - <item>0</item> - <item>1</item> - <item>2</item> - <item>4</item> - </string-array> - <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]--> <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" > <item>Use System Selection (Default)</item> @@ -240,13 +211,6 @@ <item>Stereo</item> </string-array> - <!-- Values for Bluetooth Audio Codec Channel Mode selection preference. --> - <string-array name="bluetooth_a2dp_codec_channel_mode_values" translatable="false" > - <item>0</item> - <item>1</item> - <item>2</item> - </string-array> - <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]--> <string-array name="bluetooth_a2dp_codec_channel_mode_summaries" > <item>Use System Selection (Default)</item> @@ -262,14 +226,6 @@ <item>Best Effort (Adaptive Bit Rate)</item> </string-array> - <!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. --> - <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_values" translatable="false" > - <item>1000</item> - <item>1001</item> - <item>1002</item> - <item>1003</item> - </string-array> - <!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]--> <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries" > <item>Optimized for Audio Quality</item> diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml index d2d747119bba..6b7e918ee5db 100644 --- a/packages/SettingsLib/res/values/colors.xml +++ b/packages/SettingsLib/res/values/colors.xml @@ -17,9 +17,6 @@ <resources> <color name="disabled_text_color">#66000000</color> <!-- 38% black --> - <color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color> - <color name="list_divider_color">#64000000</color> - <color name="bt_color_icon_1">#b4a50e0e</color> <!-- 72% Material Red 900 --> <color name="bt_color_icon_2">#b40d652d</color> <!-- 72% Material Green 900 --> <color name="bt_color_icon_3">#b4e37400</color> <!-- 72% Material Yellow 900 --> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 226b119ebb76..3ef3d3642262 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -19,16 +19,11 @@ <!-- The y translation to apply at the start in appear animations. --> <dimen name="appear_y_translation_start">32dp</dimen> - <!-- The translation for disappearing security views after having solved them. --> - <dimen name="disappear_y_translation">-32dp</dimen> - <!-- Height of a user icon view --> <dimen name="user_icon_view_height">24dp</dimen> <!-- User spinner --> - <dimen name="user_spinner_height">72dp</dimen> <dimen name="user_spinner_padding">4dp</dimen> <dimen name="user_spinner_padding_sides">20dp</dimen> - <dimen name="user_spinner_item_height">56dp</dimen> <!-- Lock icon for preferences locked by admin --> <dimen name="restricted_icon_padding">4dp</dimen> @@ -36,10 +31,8 @@ <dimen name="wifi_preference_badge_padding">8dip</dimen> <!-- Usage graph dimens --> - <dimen name="usage_graph_area_height">122dp</dimen> <dimen name="usage_graph_margin_top_bottom">9dp</dimen> <dimen name="usage_graph_labels_width">56dp</dimen> - <dimen name="usage_graph_labels_padding">16dp</dimen> <dimen name="usage_graph_divider_size">1dp</dimen> @@ -64,22 +57,13 @@ <!-- Ratio between width and height --> <fraction name="bt_battery_ratio_fraction">35%</fraction> - <!-- Ratio of height between battery icon and bluetooth icon --> - <fraction name="bt_battery_scale_fraction">75%</fraction> - <!-- Fraction value to smooth the edges of the battery icon. The path will be inset by this fraction of a pixel.--> <fraction name="battery_subpixel_smoothing_left">0%</fraction> <fraction name="battery_subpixel_smoothing_right">0%</fraction> - <!-- Zen mode panel: condition item button padding --> - <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> - <!-- Zen mode panel: spacing between condition items --> - <dimen name="zen_mode_condition_detail_item_spacing">12dp</dimen> <!-- Zen mode panel: spacing between two-line condition upper and lower lines --> <dimen name="zen_mode_condition_detail_item_interline_spacing">4dp</dimen> - <!-- Zen mode panel: bottom padding, a bit less than qs_panel_padding --> - <dimen name="zen_mode_condition_detail_bottom_padding">4dp</dimen> <!-- SignalDrawable --> <dimen name="signal_icon_size">15dp</dimen> @@ -93,9 +77,6 @@ <!-- Size of advanced icon --> <dimen name="advanced_icon_size">18dp</dimen> - <!-- Minimum width for the popup for updating a user's photo. --> - <dimen name="update_user_photo_popup_min_width">300dp</dimen> - <dimen name="add_a_photo_circled_padding">6dp</dimen> <dimen name="user_photo_size_in_user_info_dialog">112dp</dimen> <dimen name="add_a_photo_icon_size_in_user_info_dialog">32dp</dimen> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 06d7bb49c809..2e0155b86e7f 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -91,10 +91,6 @@ <string name="wifi_disabled_generic">Disabled</string> <!-- Status for networked disabled from a DNS or DHCP failure --> <string name="wifi_disabled_network_failure">IP Configuration Failure</string> - <!-- Status for networks disabled by the network recommendation provider --> - <string name="wifi_disabled_by_recommendation_provider">Not connected due to low quality network</string> - <!-- Status for networked disabled from a wifi association failure --> - <string name="wifi_disabled_wifi_failure">WiFi Connection Failure</string> <!-- Status for networks disabled from authentication failure (wrong password or certificate). --> <string name="wifi_disabled_password_failure">Authentication problem</string> @@ -114,20 +110,14 @@ <string name="wifi_no_internet">No internet access</string> <!-- Summary for saved networks --> <string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string> - <!-- Summary for connect to metered access point [CHAR LIMIT=NONE] --> - <string name="connected_to_metered_access_point">Connected to metered network</string> <!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] --> <string name="connected_via_network_scorer">Automatically connected via %1$s</string> <!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] --> <string name="connected_via_network_scorer_default">Automatically connected via network rating provider</string> - <!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] --> - <string name="connected_via_passpoint">Connected via %1$s</string> <!-- Status message of Wi-Fi when it is connected by a app (via suggestion or network request). [CHAR LIMIT=NONE] --> <string name="connected_via_app">Connected via <xliff:g id="name" example="Wifi App">%1$s</xliff:g></string> - <!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] --> - <string name="available_via_passpoint">Available via %1$s</string> <!-- Status message of OSU Provider network when not connected. [CHAR LIMIT=NONE] --> <string name="tap_to_sign_up">Tap to sign up</string> <!-- Package name for Settings app--> @@ -153,11 +143,6 @@ <!-- Summary for networks failing to connect due to association rejection status 17, AP full --> <string name="wifi_ap_unable_to_handle_new_sta">Access point temporarily full</string> - <!-- Status message of Wi-Fi when it is connected to a Carrier Network. [CHAR LIMIT=NONE] --> - <string name="connected_via_carrier">Connected via %1$s</string> - <!-- Status message of Wi-Fi when an available network is a carrier network. [CHAR LIMIT=NONE] --> - <string name="available_via_carrier">Available via %1$s</string> - <!-- Status message of OSU Provider upon initiating provisioning flow [CHAR LIMIT=NONE] --> <string name="osu_opening_provider">Opening <xliff:g id="passpointProvider" example="Passpoint Provider">%1$s</xliff:g></string> <!-- Status message of OSU Provider when connection fails [CHAR LIMIT=NONE] --> @@ -169,14 +154,10 @@ <!-- Status message of OSU Provider on completing provisioning. [CHAR LIMIT=NONE] --> <string name="osu_sign_up_complete">Sign-up complete. Connecting\u2026</string> - <!-- Speed label for very slow network speed --> - <string name="speed_label_very_slow">Very Slow</string> <!-- Speed label for slow network speed --> <string name="speed_label_slow">Slow</string> <!-- Speed label for okay network speed --> <string name="speed_label_okay">OK</string> - <!-- Speed label for medium network speed --> - <string name="speed_label_medium">Medium</string> <!-- Speed label for fast network speed --> <string name="speed_label_fast">Fast</string> <!-- Speed label for very fast network speed --> @@ -203,8 +184,6 @@ <string name="bluetooth_connected_no_headset">Connected (no phone)<xliff:g id="active_device">%1$s</xliff:g></string> <!-- Bluetooth settings. Message when connected to a device, except for media audio. [CHAR LIMIT=40] --> <string name="bluetooth_connected_no_a2dp">Connected (no media)<xliff:g id="active_device">%1$s</xliff:g></string> - <!-- Bluetooth settings. Message when connected to a device, except for map. [CHAR LIMIT=40] --> - <string name="bluetooth_connected_no_map">Connected (no message access)<xliff:g id="active_device">%1$s</xliff:g></string> <!-- Bluetooth settings. Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] --> <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)<xliff:g id="active_device">%1$s</xliff:g></string> diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp index 45746d9848df..cfff519705f2 100644 --- a/packages/SettingsLib/search/Android.bp +++ b/packages/SettingsLib/search/Android.bp @@ -19,11 +19,11 @@ java_plugin { name: "SettingsLib-annotation-processor", processor_class: "com.android.settingslib.search.IndexableProcessor", static_libs: [ - "javapoet-prebuilt-jar", + "javapoet", ], srcs: [ "processor-src/**/*.java", - "src/com/android/settingslib/search/SearchIndexable.java" + "src/com/android/settingslib/search/SearchIndexable.java", ], java_resource_dirs: ["resources"], } diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java index 59735f413f9d..85e8aad38808 100644 --- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java @@ -183,7 +183,7 @@ public class AccessibilityUtils { final String currentShortcutServiceId = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId); - if (currentShortcutServiceId != null) { + if (!TextUtils.isEmpty(currentShortcutServiceId)) { return currentShortcutServiceId; } return context.getString(R.string.config_defaultAccessibilityService); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index 3152e65d5a36..fa056e2b77bd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -16,6 +16,22 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING; +import static android.bluetooth.BluetoothAdapter.STATE_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * BluetoothCallback provides a callback interface for the settings @@ -33,7 +49,7 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_ON}, * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}. */ - default void onBluetoothStateChanged(int bluetoothState) {} + default void onBluetoothStateChanged(@AdapterState int bluetoothState) {} /** * It will be called when the local Bluetooth adapter has started @@ -54,14 +70,14 @@ public interface BluetoothCallback { * * @param cachedDevice the Bluetooth device. */ - default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {} + default void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {} /** * It will be called when requiring to remove a remote device from CachedBluetoothDevice list * * @param cachedDevice the Bluetooth device. */ - default void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {} + default void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {} /** * It will be called when bond state of a remote device is changed. @@ -73,7 +89,8 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothDevice#BOND_BONDING}, * {@link android.bluetooth.BluetoothDevice#BOND_BONDED}. */ - default void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {} + default void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) {} /** * It will be called in following situations: @@ -89,7 +106,9 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}, * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}. */ - default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {} + default void onConnectionStateChanged( + @Nullable CachedBluetoothDevice cachedDevice, + @ConnectionState int state) {} /** * It will be called when device been set as active for {@code bluetoothProfile} @@ -101,7 +120,8 @@ public interface BluetoothCallback { * @param activeDevice the active Bluetooth device. * @param bluetoothProfile the profile of active Bluetooth device. */ - default void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {} + default void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {} /** * It will be called in following situations: @@ -124,8 +144,10 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}. * @param bluetoothProfile the BluetoothProfile id. */ - default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { + default void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { } /** @@ -138,6 +160,24 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED}, * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED} */ - default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - } + default void onAclConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int state) {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATE_" }, value = { + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTING, + }) + @interface ConnectionState {} + + @IntDef(prefix = { "STATE_" }, value = { + STATE_OFF, + STATE_TURNING_ON, + STATE_ON, + STATE_TURNING_OFF, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AdapterState {} } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 51812f043908..a9f4e9c74103 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -32,6 +32,7 @@ import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -193,19 +194,19 @@ public class BluetoothEventManager { return deviceAdded; } - void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + void dispatchDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) { for (BluetoothCallback callback : mCallbacks) { callback.onDeviceAdded(cachedDevice); } } - void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) { + void dispatchDeviceRemoved(@NonNull CachedBluetoothDevice cachedDevice) { for (BluetoothCallback callback : mCallbacks) { callback.onDeviceDeleted(cachedDevice); } } - void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, + void dispatchProfileConnectionStateChanged(@NonNull CachedBluetoothDevice device, int state, int bluetoothProfile) { for (BluetoothCallback callback : mCallbacks) { callback.onProfileConnectionStateChanged(device, state, bluetoothProfile); @@ -228,7 +229,8 @@ public class BluetoothEventManager { } @VisibleForTesting - void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, + void dispatchActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) { boolean isActive = Objects.equals(cachedDevice, activeDevice); @@ -239,7 +241,7 @@ public class BluetoothEventManager { } } - private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state) { + private void dispatchAclStateChanged(@NonNull CachedBluetoothDevice activeDevice, int state) { for (BluetoothCallback callback : mCallbacks) { callback.onAclConnectionStateChanged(activeDevice, state); } @@ -456,6 +458,7 @@ public class BluetoothEventManager { Log.w(TAG, "ActiveDeviceChangedHandler: action is null"); return; } + @Nullable CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); int bluetoothProfile = 0; if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 766c036d521c..7353cc0393c5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -407,7 +407,7 @@ public class InfoMediaManager extends MediaManager { || sessionInfo.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED; } - private void refreshDevices() { + private synchronized void refreshDevices() { mMediaDevices.clear(); mCurrentConnectedDevice = null; if (TextUtils.isEmpty(mPackageName)) { @@ -437,7 +437,7 @@ public class InfoMediaManager extends MediaManager { return infos; } - private void buildAvailableRoutes() { + private synchronized void buildAvailableRoutes() { for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) { if (DEBUG) { Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : " @@ -447,7 +447,7 @@ public class InfoMediaManager extends MediaManager { } } - private List<MediaRoute2Info> getAvailableRoutes(String packageName) { + private synchronized List<MediaRoute2Info> getAvailableRoutes(String packageName) { final List<MediaRoute2Info> infos = new ArrayList<>(); RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName); if (routingSessionInfo != null) { @@ -571,7 +571,7 @@ public class InfoMediaManager extends MediaManager { @Override public void onSessionUpdated(RoutingSessionInfo sessionInfo) { - dispatchDataChanged(); + refreshDevices(); } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index f4af6e852580..33fb91d2252c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -666,12 +666,22 @@ public class InfoMediaManagerTest { } @Test - public void onSessionUpdated_shouldDispatchDataChanged() { + public void onSessionUpdated_shouldDispatchDeviceListAdded() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(info.isSystemRoute()).thenReturn(true); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + mShadowRouter2Manager.setAllRoutes(routes); + + mInfoMediaManager.mPackageName = ""; mInfoMediaManager.registerCallback(mCallback); mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class)); - verify(mCallback).onDeviceAttributesChanged(); + verify(mCallback).onDeviceListAdded(any()); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java index fd181ff07222..e2b242c97e0e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java @@ -65,7 +65,6 @@ public class AppHeaderPreferenceTest { @Test public void setIsInstantApp_shouldUpdateInstallType() { - mPreference.onBindViewHolder(mHolder); mPreference.setIsInstantApp(true); @@ -77,8 +76,8 @@ public class AppHeaderPreferenceTest { public void setSecondSummary_shouldUpdateSecondSummary() { final String defaultTestText = "Test second summary"; - mPreference.onBindViewHolder(mHolder); mPreference.setSecondSummary(defaultTestText); + mPreference.onBindViewHolder(mHolder); assertThat(((TextView) mRootView.findViewById(R.id.second_summary)).getText().toString()) .isEqualTo(defaultTestText); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b31e36c417ab..6c17036b3f5f 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -180,6 +180,7 @@ <uses-permission android:name="android.permission.MANAGE_ROLLBACKS" /> <uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.REBOOT" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> diff --git a/packages/Shell/src/com/android/shell/Screenshooter.java b/packages/Shell/src/com/android/shell/Screenshooter.java index 85f25528f07e..d55eda0c7062 100644 --- a/packages/Shell/src/com/android/shell/Screenshooter.java +++ b/packages/Shell/src/com/android/shell/Screenshooter.java @@ -20,6 +20,7 @@ import android.graphics.Bitmap; import android.os.IBinder; import android.util.Log; import android.view.SurfaceControl; +import android.window.ScreenCapture; /** * Helper class used to take screenshots. @@ -40,11 +41,11 @@ final class Screenshooter { Log.d(TAG, "Taking fullscreen screenshot"); // Take the screenshot final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); - final SurfaceControl.DisplayCaptureArgs captureArgs = - new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) + final ScreenCapture.DisplayCaptureArgs captureArgs = + new ScreenCapture.DisplayCaptureArgs.Builder(displayToken) .build(); - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureDisplay(captureArgs); + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureDisplay(captureArgs); final Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); if (screenShot == null) { Log.e(TAG, "Failed to take fullscreen screenshot"); diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index e9cec703c679..3325eec0c451 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -7,6 +7,7 @@ dsandler@android.com aaliomer@google.com adamcohen@google.com alexflo@google.com +arteiro@google.com asc@google.com awickham@google.com beverlyt@google.com diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 7d4dcf88542b..ebabdf571dfd 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -352,8 +352,11 @@ class ActivityLaunchAnimator( * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. + * + * If this launch animation affected the occlusion state of the keyguard, WM will provide + * us with [newKeyguardOccludedState] so that we can set the occluded state appropriately. */ - fun onLaunchAnimationCancelled() {} + fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } @VisibleForTesting @@ -667,7 +670,7 @@ class ActivityLaunchAnimator( removeTimeout() context.mainExecutor.execute { animation?.cancel() - controller.onLaunchAnimationCancelled() + controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded) } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index eac5d275092a..9656b8a99d41 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -238,7 +238,7 @@ constructor( } } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { controller.onLaunchAnimationCancelled() enableDialogDismiss() dialog.dismiss() diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt new file mode 100644 index 000000000000..496f4b3460f7 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt @@ -0,0 +1,110 @@ +/* + * 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.compose + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.android.systemui.compose.theme.LocalAndroidColorScheme + +@Composable +fun SysUiButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + content: @Composable RowScope.() -> Unit, +) { + androidx.compose.material3.Button( + modifier = modifier.padding(vertical = 6.dp).height(36.dp), + colors = filledButtonColors(), + contentPadding = ButtonPaddings, + onClick = onClick, + enabled = enabled, + ) { + content() + } +} + +@Composable +fun SysUiOutlinedButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + content: @Composable RowScope.() -> Unit, +) { + androidx.compose.material3.OutlinedButton( + modifier = modifier.padding(vertical = 6.dp).height(36.dp), + enabled = enabled, + colors = outlineButtonColors(), + border = outlineButtonBorder(), + contentPadding = ButtonPaddings, + onClick = onClick, + ) { + content() + } +} + +@Composable +fun SysUiTextButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + content: @Composable RowScope.() -> Unit, +) { + androidx.compose.material3.TextButton( + onClick = onClick, + modifier = modifier, + enabled = enabled, + content = content, + ) +} + +private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp) + +@Composable +private fun filledButtonColors(): ButtonColors { + val colors = LocalAndroidColorScheme.current + return ButtonDefaults.buttonColors( + containerColor = colors.colorAccentPrimary, + contentColor = colors.textColorOnAccent, + ) +} + +@Composable +private fun outlineButtonColors(): ButtonColors { + val colors = LocalAndroidColorScheme.current + return ButtonDefaults.outlinedButtonColors( + contentColor = colors.textColorPrimary, + ) +} + +@Composable +private fun outlineButtonBorder(): BorderStroke { + val colors = LocalAndroidColorScheme.current + return BorderStroke( + width = 1.dp, + color = colors.colorAccentPrimaryVariant, + ) +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/LatencyTrackerCompat.java b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt index e6ae19eebc72..e1f73e304b9e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/LatencyTrackerCompat.java +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -11,23 +11,21 @@ * 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 + * limitations under the License. + * */ -package com.android.systemui.shared.system; - -import android.content.Context; +package com.android.systemui.common.ui.compose -import com.android.internal.util.LatencyTracker; - -/** - * @see LatencyTracker - */ -public class LatencyTrackerCompat { +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.android.systemui.common.shared.model.Text - /** @see LatencyTracker */ - public static void logToggleRecents(Context context, int duration) { - LatencyTracker.getInstance(context).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS, - duration); +/** Returns the loaded [String] or `null` if there isn't one. */ +@Composable +fun Text.load(): String? { + return when (this) { + is Text.Loaded -> text + is Text.Resource -> stringResource(res) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt new file mode 100644 index 000000000000..3175dcfa092b --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt @@ -0,0 +1,423 @@ +/* + * 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.user.ui.compose + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.android.systemui.common.ui.compose.load +import com.android.systemui.compose.SysUiOutlinedButton +import com.android.systemui.compose.SysUiTextButton +import com.android.systemui.compose.features.R +import com.android.systemui.compose.theme.LocalAndroidColorScheme +import com.android.systemui.user.ui.viewmodel.UserActionViewModel +import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel +import com.android.systemui.user.ui.viewmodel.UserViewModel +import java.lang.Integer.min +import kotlin.math.ceil + +@Composable +fun UserSwitcherScreen( + viewModel: UserSwitcherViewModel, + onFinished: () -> Unit, + modifier: Modifier = Modifier, +) { + val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false) + val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList()) + val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1) + val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList()) + val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false) + val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false) + + UserSwitcherScreenStateless( + isFinishRequested = isFinishRequested, + users = users, + maxUserColumns = maxUserColumns, + menuActions = menuActions, + isOpenMenuButtonVisible = isOpenMenuButtonVisible, + isMenuVisible = isMenuVisible, + onMenuClosed = viewModel::onMenuClosed, + onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked, + onCancelButtonClicked = viewModel::onCancelButtonClicked, + onFinished = { + onFinished() + viewModel.onFinished() + }, + modifier = modifier, + ) +} + +@Composable +private fun UserSwitcherScreenStateless( + isFinishRequested: Boolean, + users: List<UserViewModel>, + maxUserColumns: Int, + menuActions: List<UserActionViewModel>, + isOpenMenuButtonVisible: Boolean, + isMenuVisible: Boolean, + onMenuClosed: () -> Unit, + onOpenMenuButtonClicked: () -> Unit, + onCancelButtonClicked: () -> Unit, + onFinished: () -> Unit, + modifier: Modifier = Modifier, +) { + LaunchedEffect(isFinishRequested) { + if (isFinishRequested) { + onFinished() + } + } + + Box( + modifier = + modifier + .fillMaxSize() + .padding( + horizontal = 60.dp, + vertical = 40.dp, + ), + ) { + UserGrid( + users = users, + maxUserColumns = maxUserColumns, + modifier = Modifier.align(Alignment.Center), + ) + + Buttons( + menuActions = menuActions, + isOpenMenuButtonVisible = isOpenMenuButtonVisible, + isMenuVisible = isMenuVisible, + onMenuClosed = onMenuClosed, + onOpenMenuButtonClicked = onOpenMenuButtonClicked, + onCancelButtonClicked = onCancelButtonClicked, + modifier = Modifier.align(Alignment.BottomEnd), + ) + } +} + +@Composable +private fun UserGrid( + users: List<UserViewModel>, + maxUserColumns: Int, + modifier: Modifier = Modifier, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(44.dp), + modifier = modifier, + ) { + val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt() + (0 until rowCount).forEach { rowIndex -> + Row( + horizontalArrangement = Arrangement.spacedBy(64.dp), + modifier = modifier, + ) { + val fromIndex = rowIndex * maxUserColumns + val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns) + users.subList(fromIndex, toIndex).forEach { user -> + UserItem( + viewModel = user, + ) + } + } + } + } +} + +@Composable +private fun UserItem( + viewModel: UserViewModel, +) { + val onClicked = viewModel.onClicked + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + if (onClicked != null) { + Modifier.clickable { onClicked() } + } else { + Modifier + } + .alpha(viewModel.alpha), + ) { + Box { + UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp)) + + UserItemIcon( + image = viewModel.image, + isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible, + modifier = Modifier.align(Alignment.Center).size(222.dp) + ) + } + + // User name + val text = viewModel.name.load() + if (text != null) { + // We use the box to center-align the text vertically as that is not possible with Text + // alone. + Box( + modifier = Modifier.size(width = 222.dp, height = 48.dp), + ) { + Text( + text = text, + style = MaterialTheme.typography.titleLarge, + color = colorResource(com.android.internal.R.color.system_neutral1_50), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } +} + +@Composable +private fun UserItemBackground( + modifier: Modifier = Modifier, +) { + Image( + painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground), + contentDescription = null, + modifier = modifier.clip(CircleShape), + ) +} + +@Composable +private fun UserItemIcon( + image: Drawable, + isSelectionMarkerVisible: Boolean, + modifier: Modifier = Modifier, +) { + Image( + bitmap = image.toBitmap().asImageBitmap(), + contentDescription = null, + modifier = + if (isSelectionMarkerVisible) { + // Draws a ring + modifier.border( + width = 8.dp, + color = LocalAndroidColorScheme.current.colorAccentPrimary, + shape = CircleShape, + ) + } else { + modifier + } + .padding(16.dp) + .clip(CircleShape) + ) +} + +@Composable +private fun Buttons( + menuActions: List<UserActionViewModel>, + isOpenMenuButtonVisible: Boolean, + isMenuVisible: Boolean, + onMenuClosed: () -> Unit, + onOpenMenuButtonClicked: () -> Unit, + onCancelButtonClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + ) { + // Cancel button. + SysUiTextButton( + onClick = onCancelButtonClicked, + ) { + Text(stringResource(R.string.cancel)) + } + + // "Open menu" button. + if (isOpenMenuButtonVisible) { + Spacer(modifier = Modifier.width(8.dp)) + // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it + // and the menu itself in a Box. + Box { + SysUiOutlinedButton( + onClick = onOpenMenuButtonClicked, + ) { + Text(stringResource(R.string.add)) + } + Menu( + viewModel = menuActions, + isMenuVisible = isMenuVisible, + onMenuClosed = onMenuClosed, + ) + } + } + } +} + +@Composable +private fun Menu( + viewModel: List<UserActionViewModel>, + isMenuVisible: Boolean, + onMenuClosed: () -> Unit, + modifier: Modifier = Modifier, +) { + val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4 + DropdownMenu( + expanded = isMenuVisible, + onDismissRequest = onMenuClosed, + modifier = + modifier.background( + color = MaterialTheme.colorScheme.inverseOnSurface, + ), + ) { + viewModel.forEachIndexed { index, action -> + MenuItem( + viewModel = action, + onClicked = { action.onClicked() }, + topPadding = + if (index == 0) { + 16.dp + } else { + 0.dp + }, + bottomPadding = + if (index == viewModel.size - 1) { + 16.dp + } else { + 0.dp + }, + modifier = Modifier.sizeIn(maxWidth = maxItemWidth), + ) + } + } +} + +@Composable +private fun MenuItem( + viewModel: UserActionViewModel, + onClicked: () -> Unit, + topPadding: Dp, + bottomPadding: Dp, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val density = LocalDensity.current + + val icon = + remember(viewModel.iconResourceId) { + val drawable = + checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId)) + drawable + .toBitmap( + size = with(density) { 20.dp.toPx() }.toInt(), + tintColor = Color.White, + ) + .asImageBitmap() + } + + DropdownMenuItem( + text = { + Text( + text = stringResource(viewModel.textResourceId), + style = MaterialTheme.typography.bodyMedium, + ) + }, + onClick = onClicked, + leadingIcon = { + Spacer(modifier = Modifier.width(10.dp)) + Image( + bitmap = icon, + contentDescription = null, + ) + }, + modifier = + modifier + .heightIn( + min = 56.dp, + ) + .padding( + start = 18.dp, + end = 65.dp, + top = topPadding, + bottom = bottomPadding, + ), + ) +} + +/** + * Converts the [Drawable] to a [Bitmap]. + * + * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws + * the `Drawable` onto it. Use sparingly and with care. + */ +private fun Drawable.toBitmap( + size: Int? = null, + tintColor: Color? = null, +): Bitmap { + val bitmap = + if (intrinsicWidth <= 0 || intrinsicHeight <= 0) { + Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + } else { + Bitmap.createBitmap( + size ?: intrinsicWidth, + size ?: intrinsicHeight, + Bitmap.Config.ARGB_8888 + ) + } + val canvas = Canvas(bitmap) + setBounds(0, 0, canvas.width, canvas.height) + if (tintColor != null) { + setTint(tintColor.toArgb()) + } + draw(canvas) + return bitmap +} diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg Binary files differnew file mode 100644 index 000000000000..6241b0b44bb6 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg Binary files differnew file mode 100644 index 000000000000..870ef13ee2d9 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg Binary files differnew file mode 100644 index 000000000000..bb7261c10033 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg Binary files differnew file mode 100644 index 000000000000..e34b7ddf58ce --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg Binary files differnew file mode 100644 index 000000000000..9cde24be59ef --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg Binary files differnew file mode 100644 index 000000000000..17825b639a26 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt new file mode 100644 index 000000000000..881a1def113a --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt @@ -0,0 +1,77 @@ +/* + * 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. + * + */ + +@file:OptIn(ExperimentalMaterial3Api::class) + +package com.android.systemui.compose.gallery + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.compose.SysUiButton +import com.android.systemui.compose.SysUiOutlinedButton +import com.android.systemui.compose.SysUiTextButton + +@Composable +fun ButtonsScreen( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + SysUiButton( + onClick = {}, + ) { + Text("SysUiButton") + } + + SysUiButton( + onClick = {}, + enabled = false, + ) { + Text("SysUiButton - disabled") + } + + SysUiOutlinedButton( + onClick = {}, + ) { + Text("SysUiOutlinedButton") + } + + SysUiOutlinedButton( + onClick = {}, + enabled = false, + ) { + Text("SysUiOutlinedButton - disabled") + } + + SysUiTextButton( + onClick = {}, + ) { + Text("SysUiTextButton") + } + + SysUiTextButton( + onClick = {}, + enabled = false, + ) { + Text("SysUiTextButton - disabled") + } + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt index bb98fb350a2e..6805bf83dff4 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt @@ -28,24 +28,25 @@ import com.android.systemui.compose.theme.SystemUITheme /** The gallery app screens. */ object GalleryAppScreens { - val Typography = ChildScreen("typography") { TypographyScreen() } - val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } - val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } - val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } + private val Typography = ChildScreen("typography") { TypographyScreen() } + private val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } + private val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } + private val Buttons = ChildScreen("buttons") { ButtonsScreen() } + private val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } - val PeopleEmpty = + private val PeopleEmpty = ChildScreen("people_empty") { navController -> EmptyPeopleScreen(onResult = { navController.popBackStack() }) } - val PeopleFew = + private val PeopleFew = ChildScreen("people_few") { navController -> FewPeopleScreen(onResult = { navController.popBackStack() }) } - val PeopleFull = + private val PeopleFull = ChildScreen("people_full") { navController -> FullPeopleScreen(onResult = { navController.popBackStack() }) } - val People = + private val People = ParentScreen( "people", mapOf( @@ -54,6 +55,52 @@ object GalleryAppScreens { "Full" to PeopleFull, ) ) + private val UserSwitcherSingleUser = + ChildScreen("user_switcher_single") { navController -> + UserSwitcherScreen( + userCount = 1, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherThreeUsers = + ChildScreen("user_switcher_three") { navController -> + UserSwitcherScreen( + userCount = 3, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherFourUsers = + ChildScreen("user_switcher_four") { navController -> + UserSwitcherScreen( + userCount = 4, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherFiveUsers = + ChildScreen("user_switcher_five") { navController -> + UserSwitcherScreen( + userCount = 5, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcherSixUsers = + ChildScreen("user_switcher_six") { navController -> + UserSwitcherScreen( + userCount = 6, + onFinished = navController::popBackStack, + ) + } + private val UserSwitcher = + ParentScreen( + "user_switcher", + mapOf( + "Single" to UserSwitcherSingleUser, + "Three" to UserSwitcherThreeUsers, + "Four" to UserSwitcherFourUsers, + "Five" to UserSwitcherFiveUsers, + "Six" to UserSwitcherSixUsers, + ) + ) val Home = ParentScreen( @@ -63,7 +110,9 @@ object GalleryAppScreens { "Material colors" to MaterialColors, "Android colors" to AndroidColors, "Example feature" to ExampleFeature, + "Buttons" to Buttons, "People" to People, + "User Switcher" to UserSwitcher, ) ) } diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt new file mode 100644 index 000000000000..fe9707d22684 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt @@ -0,0 +1,35 @@ +/* + * 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.compose.gallery + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import com.android.systemui.user.Fakes.fakeUserSwitcherViewModel +import com.android.systemui.user.ui.compose.UserSwitcherScreen + +@Composable +fun UserSwitcherScreen( + userCount: Int, + onFinished: () -> Unit, +) { + val context = LocalContext.current.applicationContext + UserSwitcherScreen( + viewModel = fakeUserSwitcherViewModel(context, userCount = userCount), + onFinished = onFinished, + ) +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt index 02d76f404a32..91a73ea16dc4 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt @@ -58,12 +58,29 @@ object Fakes { (0 until userCount).map { index -> UserModel( id = index, - name = Text.Loaded("user_$index"), + name = + Text.Loaded( + when (index % 6) { + 0 -> "Ross Geller" + 1 -> "Phoebe Buffay" + 2 -> "Monica Geller" + 3 -> "Rachel Greene" + 4 -> "Chandler Bing" + else -> "Joey Tribbiani" + } + ), image = checkNotNull( AppCompatResources.getDrawable( context, - R.drawable.ic_avatar_guest_user + when (index % 6) { + 0 -> R.drawable.kitten1 + 1 -> R.drawable.kitten2 + 2 -> R.drawable.kitten3 + 3 -> R.drawable.kitten4 + 4 -> R.drawable.kitten5 + else -> R.drawable.kitten6 + }, ) ), isSelected = index == 0, diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index bfbccb85b13f..7839ec832e2c 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -498,7 +498,6 @@ -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index 1aaf19e8793f..c50340cfd247 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -147,10 +147,10 @@ public interface FalsingManager { } /** - * Listener that is alerted when a double tap is required to confirm a single tap. + * Listener that is alerted when an additional tap is required to confirm a single tap. **/ interface FalsingTapListener { - void onDoubleTapRequired(); + void onAdditionalTapRequired(); } /** Passed to {@link FalsingManager#onProximityEvent}. */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt index 8bf982d360a1..9c7fbe8842bc 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt @@ -3,7 +3,9 @@ package com.android.systemui.plugins.qs interface QSContainerController { fun setCustomizerAnimating(animating: Boolean) - fun setCustomizerShowing(showing: Boolean) + fun setCustomizerShowing(showing: Boolean) = setCustomizerShowing(showing, 0L) + + fun setCustomizerShowing(showing: Boolean, animationDuration: Long) fun setDetailShowing(showing: Boolean) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml b/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml new file mode 100644 index 000000000000..732fc57dbded --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml @@ -0,0 +1,43 @@ +<?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. + --> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="6dp" + android:insetBottom="6dp"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?android:attr/textColorSecondary"/> + <corners android:radius="24dp"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="24dp"/> + <solid android:color="@android:color/transparent"/> + <stroke android:color="?androidprv:attr/colorAccentTertiary" + android:width="1dp" + /> + <padding android:left="16dp" + android:top="12dp" + android:right="16dp" + android:bottom="12dp"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml new file mode 100644 index 000000000000..a34712386d52 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml @@ -0,0 +1,109 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="17" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " + android:valueTo="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="33" + android:propertyName="pathData" + android:startOffset="17" + android:valueFrom="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c " + android:valueTo="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="50" + android:valueFrom="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c " + android:valueTo="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="83" + android:propertyName="pathData" + android:startOffset="117" + android:valueFrom="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c " + android:valueTo="M0 -8.18 C-1.81,-7.06 -3.89,-5.21 -5.95,-2.81 C-7.56,-0.94 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.99 0,7.99 C0,6.34 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-6 0,-8.18c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19 M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml new file mode 100644 index 000000000000..c5609d8e465b --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml @@ -0,0 +1,109 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " + android:valueTo="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="100" + android:valueFrom="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueTo="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="167" + android:valueFrom="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c " + android:valueTo="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="233" + android:valueFrom="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c " + android:valueTo="M5.63 -2.97 C3.65,-4.93 0.75,-8.37 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 4.13,7.94 6.31,3.44 C6.68,2.66 7,1.37 6.87,0.06 C6.77,-0.84 6.13,-2.47 5.63,-2.97c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19 M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_light_dark_theme_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_light_dark_theme_icon_off.xml new file mode 100644 index 000000000000..5c0a7c820887 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_light_dark_theme_icon_off.xml @@ -0,0 +1,109 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="20" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M7.38 -2.03 C6.38,-4.66 3.47,-7.32 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 5.22,6.17 7.37,2.06 C7.37,1.21 7.37,0.69 7.37,0 C7.37,-0.91 7.38,-1.16 7.38,-2.03c " + android:valueTo="M7.25 -2.44 C5.46,-5.86 1.53,-8.3 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.43,6.91 7.28,2.47 C7.29,1.42 7.27,1.3 7.3,0 C7.32,-1.17 7.27,-1.49 7.25,-2.44c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="40" + android:propertyName="pathData" + android:startOffset="20" + android:valueFrom="M7.25 -2.44 C5.46,-5.86 1.53,-8.3 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.43,6.91 7.28,2.47 C7.29,1.42 7.27,1.3 7.3,0 C7.32,-1.17 7.27,-1.49 7.25,-2.44c " + android:valueTo="M7.06 -3.94 C3.5,-8.81 -3.31,-9.44 -7,-3.66 C-7.12,-3.47 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 7.12,-2.75 7.06,-3.94c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="40" + android:propertyName="pathData" + android:startOffset="60" + android:valueFrom="M7.06 -3.94 C3.5,-8.81 -3.31,-9.44 -7,-3.66 C-7.12,-3.47 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 7.12,-2.75 7.06,-3.94c " + android:valueTo="M3.11 -6.83 C-0.23,-9.49 -6.06,-6.24 -7.23,-2.29 C-7.28,-2.09 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="100" + android:valueFrom="M3.11 -6.83 C-0.23,-9.49 -6.06,-6.24 -7.23,-2.29 C-7.28,-2.09 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueTo="M0 -7.49 C-2.58,-7.49 -7.45,-4.78 -7.45,-1.62 C-7.45,-1.42 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="217" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.08 C-2.69,-7.08 -7.03,-5.19 -7.03,0.03 C-7.03,5.25 -2.19,7.08 0,7.08 C3.03,7.08 7.08,3.91 7.08,0 C7.08,-3.91 3.69,-7.08 0,-7.08c M-8.33 0 C-8.33,-4.6 -4.6,-8.33 0,-8.33 C4.6,-8.33 8.33,-4.6 8.33,0 C8.33,4.6 4.6,8.33 0,8.33 C-4.6,8.33 -8.33,4.6 -8.33,0c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M7.38 -2.03 C6.38,-4.66 3.47,-7.32 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 5.22,6.17 7.37,2.06 C7.37,1.21 7.37,0.69 7.37,0 C7.37,-0.91 7.38,-1.16 7.38,-2.03c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_light_dark_theme_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_light_dark_theme_icon_on.xml new file mode 100644 index 000000000000..a96b7480ec94 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_light_dark_theme_icon_on.xml @@ -0,0 +1,109 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M0 -7.49 C-2.58,-7.49 -7.45,-4.78 -7.45,-1.62 C-7.45,-1.42 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " + android:valueTo="M3.11 -6.83 C-0.23,-9.49 -6.06,-6.24 -7.23,-2.29 C-7.28,-2.09 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="100" + android:valueFrom="M3.11 -6.83 C-0.23,-9.49 -6.06,-6.24 -7.23,-2.29 C-7.28,-2.09 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueTo="M7.06 -3.94 C3.5,-8.81 -3.31,-9.44 -7,-3.66 C-7.12,-3.47 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 7.12,-2.75 7.06,-3.94c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="167" + android:valueFrom="M7.06 -3.94 C3.5,-8.81 -3.31,-9.44 -7,-3.66 C-7.12,-3.47 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 7.12,-2.75 7.06,-3.94c " + android:valueTo="M7.25 -2.44 C5.46,-5.86 1.53,-8.3 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.43,6.91 7.28,2.47 C7.29,1.42 7.27,1.3 7.3,0 C7.32,-1.17 7.27,-1.49 7.25,-2.44c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="233" + android:valueFrom="M7.25 -2.44 C5.46,-5.86 1.53,-8.3 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.43,6.91 7.28,2.47 C7.29,1.42 7.27,1.3 7.3,0 C7.32,-1.17 7.27,-1.49 7.25,-2.44c " + android:valueTo="M7.38 -2.03 C6.38,-4.66 3.47,-7.32 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 5.22,6.17 7.37,2.06 C7.37,1.21 7.37,0.69 7.37,0 C7.37,-0.91 7.38,-1.16 7.38,-2.03c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M0 -7.08 C-2.69,-7.08 -7.03,-5.19 -7.03,0.03 C-7.03,5.25 -2.19,7.08 0,7.08 C3.03,7.08 7.08,3.91 7.08,0 C7.08,-3.91 3.69,-7.08 0,-7.08c M-8.33 0 C-8.33,-4.6 -4.6,-8.33 0,-8.33 C4.6,-8.33 8.33,-4.6 8.33,0 C8.33,4.6 4.6,8.33 0,8.33 C-4.6,8.33 -8.33,4.6 -8.33,0c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M0 -7.49 C-2.58,-7.49 -7.45,-4.78 -7.45,-1.62 C-7.45,-1.42 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml index db508c91e0de..67fd5b9a78bd 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml @@ -17,15 +17,17 @@ */ --> -<!-- This contains disable esim buttonas shared by sim_pin/sim_puk screens --> -<com.android.keyguard.KeyguardEsimArea - xmlns:android="http://schemas.android.com/apk/res/android" +<!-- This contains disable eSim buttons shared by sim_pin/sim_puk screens --> +<com.android.keyguard.KeyguardEsimArea xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_disable_esim" + style="@style/Keyguard.TextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:id="@+id/keyguard_disable_esim" - android:visibility="gone" + android:background="@drawable/kg_bouncer_secondary_button" + android:drawablePadding="10dp" + android:drawableStart="@drawable/ic_no_sim" + android:drawableTint="?android:attr/textColorPrimary" android:text="@string/disable_carrier_button_text" - style="@style/Keyguard.TextView" - android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/kg_status_line_font_size" - android:textAllCaps="@bool/kg_use_all_caps" /> + android:visibility="gone" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml index f2fe520f340f..7db0fe908ec0 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml @@ -47,8 +47,7 @@ <include layout="@layout/keyguard_esim_area" android:id="@+id/keyguard_esim_area" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/eca_overlap" /> + android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/row0" android:layout_width="match_parent" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml index a21ec29267fe..422bd4c12e8e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml @@ -51,8 +51,7 @@ <include layout="@layout/keyguard_esim_area" android:id="@+id/keyguard_esim_area" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/eca_overlap" /> + android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/row0" diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index e80cfafdd71a..a1068c65bae2 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -31,8 +31,4 @@ <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">100dp</dimen> <dimen name="widget_label_font_size">18sp</dimen> - - <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. - Should be 0 on devices with plenty of room (e.g. tablets) --> - <dimen name="eca_overlap">0dip</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 32871f0abb4f..46f6ab2399d1 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -44,10 +44,6 @@ <dimen name="keyguard_eca_top_margin">18dp</dimen> <dimen name="keyguard_eca_bottom_margin">12dp</dimen> - <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. - Should be 0 on devices with plenty of room (e.g. tablets) --> - <dimen name="eca_overlap">-10dip</dimen> - <!-- Slice header --> <dimen name="widget_title_font_size">20dp</dimen> <dimen name="widget_title_line_height">24dp</dimen> @@ -84,6 +80,10 @@ <!-- The translation for disappearing security views after having solved them. --> <dimen name="disappear_y_translation">-32dp</dimen> + <!-- Dimens for animation for the Bouncer PIN view --> + <dimen name="pin_view_trans_y_entry">120dp</dimen> + <dimen name="pin_view_trans_y_entry_offset">10dp</dimen> + <!-- Spacing around each button used for PIN view --> <dimen name="num_pad_key_width">72dp</dimen> <dimen name="num_pad_entry_row_margin_bottom">12dp</dimen> diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml index 1ab0d4d3bf12..92c952794f57 100644 --- a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml +++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml @@ -14,12 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="24" - android:viewportHeight="24" - android:width="24dp" - android:height="24dp"> - <path - android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 15L16.75 15L16.75 6L4 6L4 15Z" - android:fillType="evenOdd" - android:fillColor="#000000" /> -</vector>
\ No newline at end of file + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M9.7,29.6H33.75V13.7H9.7ZM7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H41Q42.2,8 43.1,8.9Q44,9.8 44,11V37Q44,38.2 43.1,39.1Q42.2,40 41,40ZM7,37H41Q41,37 41,37Q41,37 41,37V11Q41,11 41,11Q41,11 41,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml index 6fc89a69f36e..209681f29548 100644 --- a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml +++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml @@ -14,12 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="24" - android:viewportHeight="24" - android:width="24dp" - android:height="24dp"> - <path - android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 12.75L13.75 12.75L13.75 6L4 6L4 12.75Z" - android:fillType="evenOdd" - android:fillColor="#000000" /> -</vector>
\ No newline at end of file + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M9.7,27.6H27.75V13.7H9.7ZM7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H41Q42.2,8 43.1,8.9Q44,9.8 44,11V37Q44,38.2 43.1,39.1Q42.2,40 41,40ZM7,37H41Q41,37 41,37Q41,37 41,37V11Q41,11 41,11Q41,11 41,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml index fd7357424f32..a3dc816f6755 100644 --- a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml +++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml @@ -14,12 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="24" - android:viewportHeight="24" - android:width="24dp" - android:height="24dp"> - <path - android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 10.5L8.5 10.5L8.5 6L4 6L4 10.5Z" - android:fillType="evenOdd" - android:fillColor="#000000" /> -</vector>
\ No newline at end of file + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M9.7,21.6H17.75V13.7H9.7ZM7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H41Q42.2,8 43.1,8.9Q44,9.8 44,11V37Q44,38.2 43.1,39.1Q42.2,40 41,40ZM7,37H41Q41,37 41,37Q41,37 41,37V11Q41,11 41,11Q41,11 41,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml index 1bff559ff3df..dfedd288619c 100644 --- a/packages/SystemUI/res/drawable/ic_move_magnification.xml +++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml @@ -1,43 +1,25 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> + Copyright (C) 2020 The Android Open Source Project -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item> - <shape android:shape="rectangle"> - <solid - android:color="@color/magnification_drag_handle_color" /> - <size - android:height="@dimen/magnification_drag_view_size" - android:width="@dimen/magnification_drag_view_size"/> - <corners android:topLeftRadius="12dp"/> + 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 - </shape> - </item> - <item - android:gravity="center"> + http://www.apache.org/licenses/LICENSE-2.0 - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="24" - android:viewportHeight="24" - android:width="24dp" - android:height="24dp"> - <path - android:pathData="M13.2217 21.7734C12.8857 22.1094 12.288 22.712 12 23C12 23 11.1143 22.1094 10.7783 21.7734L8.33494 19.3301L9.55662 18.1084L12 20.5518L14.4434 18.1084L15.665 19.3301L13.2217 21.7734ZM19.3301 15.665L18.1084 14.4433L20.5518 12L18.1084 9.5566L19.3301 8.33492L21.7735 10.7783C22.1094 11.1142 22.4241 11.4241 23 12C22.4241 12.5759 22.3963 12.5988 21.7735 13.2217L19.3301 15.665ZM14.4434 14.4433C13.7714 15.1153 12.957 15.4512 12 15.4512C11.043 15.4512 10.2285 15.1153 9.55662 14.4433C8.88469 13.7714 8.54873 12.957 8.54873 12C8.54873 11.043 8.88469 10.2285 9.55662 9.5566C10.2285 8.88468 11.043 8.54871 12 8.54871C12.957 8.54871 13.7714 8.88467 14.4434 9.5566C15.1153 10.2285 15.4512 11.043 15.4512 12C15.4512 12.957 15.1153 13.7714 14.4434 14.4433ZM4.66988 15.665L2.22651 13.2217C1.89055 12.8857 1.28791 12.288 1 12C1.28791 11.712 1.89055 11.1143 2.22651 10.7783L4.66988 8.33492L5.89157 9.5566L3.4482 12L5.89157 14.4433L4.66988 15.665ZM14.4434 5.89155L12 3.44818L9.55662 5.89155L8.33494 4.66987L10.7783 2.2265C11.1389 1.86592 11.2963 1.70369 12 1C12.5758 1.57585 12.8857 1.89053 13.2217 2.2265L15.665 4.66986L14.4434 5.89155Z" - android:fillColor="#ffffff" /> - </vector> - </item> -</layer-list> + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,15Q10.75,15 9.875,14.125Q9,13.25 9,12Q9,10.75 9.875,9.875Q10.75,9 12,9Q13.25,9 14.125,9.875Q15,10.75 15,12Q15,13.25 14.125,14.125Q13.25,15 12,15ZM12,22 L7.75,17.75 9.15,16.35 12,19.15 14.85,16.35 16.25,17.75ZM6.25,16.25 L2,12 6.25,7.75 7.65,9.15 4.85,12 7.65,14.85ZM9.15,7.65 L7.75,6.25 12,2 16.25,6.25 14.85,7.65 12,4.85ZM17.75,16.25 L16.35,14.85 19.15,12 16.35,9.15 17.75,7.75 22,12Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_no_sim.xml b/packages/SystemUI/res/drawable/ic_no_sim.xml new file mode 100644 index 000000000000..ccfb6ea642cc --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_no_sim.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M20,17.175 L18,15.175V4Q18,4 18,4Q18,4 18,4H10.85L8.85,6L7.4,4.6L10,2H18Q18.825,2 19.413,2.587Q20,3.175 20,4ZM20.5,23.3 L6,8.8V20Q6,20 6,20Q6,20 6,20H18Q18,20 18,20Q18,20 18,20V17.975L20,19.975V20Q20,20.825 19.413,21.413Q18.825,22 18,22H6Q5.175,22 4.588,21.413Q4,20.825 4,20V8L4.6,7.4L0.7,3.5L2.125,2.1L21.9,21.875ZM13.525,10.675Q13.525,10.675 13.525,10.675Q13.525,10.675 13.525,10.675ZM11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/media_output_dialog_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_background.xml new file mode 100644 index 000000000000..40bfd83038af --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_dialog_background.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners + android:radius="28dp"/> + <solid android:color="@color/media_dialog_background" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml new file mode 100644 index 000000000000..57777a670d33 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml @@ -0,0 +1,84 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="150" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:pivotY="1" + android:rotation="180" + android:translateX="12" + android:translateY="10.984"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="1440" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml new file mode 100644 index 000000000000..b3764137793a --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml @@ -0,0 +1,110 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="333" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " + android:valueTo="M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.2,0.2 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="613.191" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.086,0.422 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="417" + android:propertyName="rotation" + android:startOffset="500" + android:valueFrom="613.191" + android:valueTo="720" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.334,1.012 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="933" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="12" + android:translateY="10.75"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="0" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml b/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml new file mode 100644 index 000000000000..b0f85e7714c6 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml @@ -0,0 +1,197 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="strokeColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueTo="M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c " + android:valueTo="M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueTo="M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="517" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:pathData=" M93 40 C95.21,40 97,41.79 97,44 C97,46.21 95.21,48 93,48 C90.79,48 89,46.21 89,44 C89,41.79 90.79,40 93,40c " + android:strokeAlpha="1" + android:strokeColor="#000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml b/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml new file mode 100644 index 000000000000..5546b6f39813 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml @@ -0,0 +1,197 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="strokeColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueTo="M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c " + android:valueTo="M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueTo="M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="517" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:pathData=" M93 40 C95.21,40 97,41.79 97,44 C97,46.21 95.21,48 93,48 C90.79,48 89,46.21 89,44 C89,41.79 90.79,40 93,40c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml new file mode 100644 index 000000000000..0769a85dae49 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml @@ -0,0 +1,94 @@ +<?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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="283" + android:propertyName="translateY" + android:startOffset="0" + android:valueFrom="12.125" + android:valueTo="12.312" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="283" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="-45" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="300" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G_T_1" + android:rotation="-45" + android:translateX="12.875" + android:translateY="12.125"> + <group + android:name="_R_G_L_0_G" + android:translateX="-2.375"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.59,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.15,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.95,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.12,8.44 4.31,8.56 3.33,8.62c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml new file mode 100644 index 000000000000..5ffe262d8077 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml @@ -0,0 +1,94 @@ +<?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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="translateY" + android:startOffset="0" + android:valueFrom="12.312" + android:valueTo="12.125" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="-45" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G_T_1" + android:rotation="0" + android:translateX="12.875" + android:translateY="12.312"> + <group + android:name="_R_G_L_0_G" + android:translateX="-2.375"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.6,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.14,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.94,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.13,8.43 4.31,8.56 3.33,8.62c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index c972624c04b1..006b260434bc 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -40,7 +40,6 @@ android:layout_height="match_parent" android:orientation="horizontal" android:gravity="center_vertical" - android:layout_marginEnd="@dimen/dream_overlay_status_bar_extra_margin" app:layout_constraintEnd_toStartOf="@+id/dream_overlay_system_status" /> <LinearLayout @@ -48,14 +47,15 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" - android:layout_marginStart="@dimen/dream_overlay_status_bar_extra_margin" + android:paddingStart="@dimen/dream_overlay_status_bar_extra_margin" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent"> <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/dream_overlay_alarm_set" android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/ic_alarm" android:tint="@android:color/white" android:visibility="gone" @@ -65,7 +65,7 @@ android:id="@+id/dream_overlay_priority_mode" android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" @@ -75,7 +75,7 @@ android:id="@+id/dream_overlay_wifi_status" android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/ic_signal_wifi_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> @@ -84,7 +84,7 @@ android:id="@+id/dream_overlay_mic_off" android:layout_width="@dimen/dream_overlay_grey_chip_width" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_mic_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_mic_off" /> @@ -93,7 +93,7 @@ android:id="@+id/dream_overlay_camera_off" android:layout_width="@dimen/dream_overlay_grey_chip_width" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_camera_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_camera_off" /> @@ -102,7 +102,7 @@ android:id="@+id/dream_overlay_camera_mic_off" android:layout_width="@dimen/dream_overlay_grey_chip_width" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_mic_and_camera_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" /> diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index 93c16e4e119d..b76de5afdb73 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -20,6 +20,7 @@ android:id="@+id/media_output_dialog" android:layout_width="@dimen/large_dialog_width" android:layout_height="wrap_content" + android:background="@drawable/media_output_dialog_background" android:orientation="vertical"> <LinearLayout diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml index 499e24e6ecbd..c67ac8d34aa6 100644 --- a/packages/SystemUI/res/values/bools.xml +++ b/packages/SystemUI/res/values/bools.xml @@ -18,4 +18,6 @@ <resources> <!-- Whether to show the user switcher in quick settings when only a single user is present. --> <bool name="qs_show_user_switcher_for_single_user">false</bool> + <!-- Whether to show a custom biometric prompt size--> + <bool name="use_custom_bp_size">false</bool> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index dd5987bfe1c2..9e8bef06270b 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -186,7 +186,7 @@ <color name="media_dialog_item_main_content">@color/material_dynamic_primary20</color> <color name="media_dialog_item_background">@color/material_dynamic_secondary95</color> <color name="media_dialog_connected_item_background">@color/material_dynamic_primary90</color> - <color name="media_dialog_seekbar_progress">@color/material_dynamic_secondary40</color> + <color name="media_dialog_seekbar_progress">@android:color/system_accent1_200</color> <color name="media_dialog_button_background">@color/material_dynamic_primary40</color> <color name="media_dialog_solid_button_text">@color/material_dynamic_neutral95</color> @@ -245,6 +245,10 @@ <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color> <color name="dream_overlay_aqi_hazardous">#880E4F</color> <color name="dream_overlay_aqi_unknown">#BDC1C6</color> + + <!-- Dream overlay text shadows --> <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color> <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color> + <color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color> + <color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index a8027238a0bf..37549c927a90 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -611,6 +611,33 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> + <!-- Messages that should NOT be shown to the user during face authentication on keyguard. + This includes both lockscreen and bouncer. This should be used to hide messages that may be + too chatty or messages that the user can't do much about. Entries are defined in + android.hardware.biometrics.face@1.0 types.hal. + + Although not visibly shown to the user, these acquired messages (sent per face auth frame) + are still counted towards the total frames to determine whether a deferred message + (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on + face timeout. --> + <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" > + </integer-array> + + <!-- Which face help messages to defer until face auth times out. If face auth is cancelled + or ends on another error, then the message is never surfaced. May also never surface + if it doesn't meet a threshold % of authentication frames specified by. + config_face_help_msgs_defer_until_timeout_threshold. --> + <integer-array name="config_face_help_msgs_defer_until_timeout"> + </integer-array> + + <!-- Percentage of face auth frames received required to show a deferred message at + FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages + that are deferred.--> + <item name="config_face_help_msgs_defer_until_timeout_threshold" + translatable="false" format="float" type="dimen"> + .75 + </item> + <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9cb4cc4c8e63..6592e143dc1b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -520,7 +520,7 @@ <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen> <dimen name="qs_tile_margin_top_bottom">4dp</dimen> <dimen name="qs_brightness_margin_top">8dp</dimen> - <dimen name="qs_brightness_margin_bottom">24dp</dimen> + <dimen name="qs_brightness_margin_bottom">16dp</dimen> <dimen name="qqs_layout_margin_top">16dp</dimen> <dimen name="qqs_layout_padding_bottom">24dp</dimen> @@ -945,6 +945,8 @@ <dimen name="biometric_dialog_medium_to_large_translation_offset">100dp</dimen> <!-- Y translation for credential contents when animating in --> <dimen name="biometric_dialog_credential_translation_offset">60dp</dimen> + <dimen name="biometric_dialog_width">240dp</dimen> + <dimen name="biometric_dialog_height">240dp</dimen> <!-- Biometric Auth Credential values --> <dimen name="biometric_auth_icon_size">48dp</dimen> @@ -1475,7 +1477,7 @@ <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen> <dimen name="dream_overlay_notification_indicator_size">6dp</dimen> <dimen name="dream_overlay_grey_chip_width">56dp</dimen> - <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen> + <dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen> <!-- Dream overlay complications related dimensions --> <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen> @@ -1558,10 +1560,20 @@ <dimen name="broadcast_dialog_btn_text_size">16sp</dimen> <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen> <dimen name="broadcast_dialog_margin">16dp</dimen> + + <!-- Shadow for dream overlay clock complication --> <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen> - <dimen name="dream_overlay_clock_key_text_shadow_radius">5dp</dimen> + <dimen name="dream_overlay_clock_key_text_shadow_radius">3dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_dy">0dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_radius">1dp</dimen> + + <!-- Shadow for dream overlay status bar complications --> + <dimen name="dream_overlay_status_bar_key_text_shadow_dx">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_dy">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_radius">1dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> </resources> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt index cbab0a75061e..fafc7744f439 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt @@ -23,9 +23,11 @@ import platform.test.screenshot.PathConfig /** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */ class SystemUIGoldenImagePathManager( pathConfig: PathConfig, + override val assetsPathRelativeToRepo: String = "tests/screenshot/assets" ) : GoldenImagePathManager( appContext = InstrumentationRegistry.getInstrumentation().context, + assetsPathRelativeToRepo = assetsPathRelativeToRepo, deviceLocalPath = InstrumentationRegistry.getInstrumentation() .targetContext diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java index 07733473f298..0b0df833e916 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java @@ -17,11 +17,10 @@ package com.android.systemui.shared.recents.model; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.graphics.Bitmap.Config.ARGB_8888; -import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_UNDEFINED; - import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java deleted file mode 100644 index 0f937bdfe449..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2018 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.shared.system; - -import android.app.Activity; -import android.view.View; -import android.view.ViewHierarchyEncoder; - -import java.io.ByteArrayOutputStream; - -public class ActivityCompat { - private final Activity mWrapped; - - public ActivityCompat(Activity activity) { - mWrapped = activity; - } - - /** - * @see Activity#registerRemoteAnimations - */ - public void registerRemoteAnimations(RemoteAnimationDefinitionCompat definition) { - mWrapped.registerRemoteAnimations(definition.getWrapped()); - } - - /** - * @see Activity#unregisterRemoteAnimations - */ - public void unregisterRemoteAnimations() { - mWrapped.unregisterRemoteAnimations(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java deleted file mode 100644 index be99b270c09a..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.shared.system; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - -import android.app.ActivityOptions; -import android.content.Context; -import android.os.Handler; - -/** - * Wrapper around internal ActivityOptions creation. - */ -public abstract class ActivityOptionsCompat { - - /** - * @return ActivityOptions for starting a task in freeform. - */ - public static ActivityOptions makeFreeformOptions() { - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); - return options; - } - - public static ActivityOptions makeRemoteAnimation( - RemoteAnimationAdapterCompat remoteAnimationAdapter) { - return ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter.getWrapped(), - remoteAnimationAdapter.getRemoteTransition().getTransition()); - } - - /** - * Constructs an ActivityOptions object that will delegate its transition handling to a - * `remoteTransition`. - */ - public static ActivityOptions makeRemoteTransition(RemoteTransitionCompat remoteTransition) { - return ActivityOptions.makeRemoteTransition(remoteTransition.getTransition()); - } - - /** - * Returns ActivityOptions for overriding task transition animation. - */ - public static ActivityOptions makeCustomAnimation(Context context, int enterResId, - int exitResId, final Runnable callback, final Handler callbackHandler) { - return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId, - callbackHandler, - new ActivityOptions.OnAnimationStartedListener() { - @Override - public void onAnimationStarted(long elapsedRealTime) { - if (callback != null) { - callbackHandler.post(callback); - } - } - }, null /* finishedListener */); - } - - /** - * Sets the flag to freeze the recents task list reordering as a part of launching the activity. - */ - public static ActivityOptions setFreezeRecentTasksList(ActivityOptions opts) { - opts.setFreezeRecentTasksReordering(); - return opts; - } - - /** - * Sets the launch event time from launcher. - */ - public static ActivityOptions setLauncherSourceInfo(ActivityOptions opts, long uptimeMillis) { - opts.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER, uptimeMillis); - return opts; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 33e8e3555eba..09cf7c57c08a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -58,7 +58,7 @@ public class RemoteAnimationAdapterCompat { mRemoteTransition = buildRemoteTransition(runner, appThread); } - RemoteAnimationAdapter getWrapped() { + public RemoteAnimationAdapter getWrapped() { return mWrapped; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java index 098698aa1c5c..ab55037159ef 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java @@ -34,7 +34,7 @@ public class RemoteAnimationDefinitionCompat { mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped()); } - RemoteAnimationDefinition getWrapped() { + public RemoteAnimationDefinition getWrapped() { return mWrapped; } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java deleted file mode 100644 index cfb23f9e4f9c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.system; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Rect; -import android.graphics.Region; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; - -import java.util.HashMap; - -public class ViewTreeObserverWrapper { - - private static final HashMap<OnComputeInsetsListener, ViewTreeObserver> - sListenerObserverMap = new HashMap<>(); - private static final HashMap<OnComputeInsetsListener, OnComputeInternalInsetsListener> - sListenerInternalListenerMap = new HashMap<>(); - - /** - * Register a callback to be invoked when the invoked when it is time to compute the window's - * insets. - * - * @param observer The observer to be added - * @param listener The callback to add - * @throws IllegalStateException If {@link ViewTreeObserver#isAlive()} returns false - */ - public static void addOnComputeInsetsListener( - @NonNull ViewTreeObserver observer, @NonNull OnComputeInsetsListener listener) { - final OnComputeInternalInsetsListener internalListener = internalInOutInfo -> { - final InsetsInfo inOutInfo = new InsetsInfo(); - inOutInfo.contentInsets.set(internalInOutInfo.contentInsets); - inOutInfo.visibleInsets.set(internalInOutInfo.visibleInsets); - inOutInfo.touchableRegion.set(internalInOutInfo.touchableRegion); - listener.onComputeInsets(inOutInfo); - internalInOutInfo.contentInsets.set(inOutInfo.contentInsets); - internalInOutInfo.visibleInsets.set(inOutInfo.visibleInsets); - internalInOutInfo.touchableRegion.set(inOutInfo.touchableRegion); - internalInOutInfo.setTouchableInsets(inOutInfo.mTouchableInsets); - }; - sListenerObserverMap.put(listener, observer); - sListenerInternalListenerMap.put(listener, internalListener); - observer.addOnComputeInternalInsetsListener(internalListener); - } - - /** - * Remove a previously installed insets computation callback. - * - * @param victim The callback to remove - * @throws IllegalStateException If {@link ViewTreeObserver#isAlive()} returns false - * @see #addOnComputeInsetsListener(ViewTreeObserver, OnComputeInsetsListener) - */ - public static void removeOnComputeInsetsListener(@NonNull OnComputeInsetsListener victim) { - final ViewTreeObserver observer = sListenerObserverMap.get(victim); - final OnComputeInternalInsetsListener listener = sListenerInternalListenerMap.get(victim); - if (observer != null && listener != null) { - observer.removeOnComputeInternalInsetsListener(listener); - } - sListenerObserverMap.remove(victim); - sListenerInternalListenerMap.remove(victim); - } - - /** - * Interface definition for a callback to be invoked when layout has - * completed and the client can compute its interior insets. - */ - public interface OnComputeInsetsListener { - /** - * Callback method to be invoked when layout has completed and the - * client can compute its interior insets. - * - * @param inoutInfo Should be filled in by the implementation with - * the information about the insets of the window. This is called - * with whatever values the previous OnComputeInsetsListener - * returned, if there are multiple such listeners in the window. - */ - void onComputeInsets(InsetsInfo inoutInfo); - } - - /** - * Parameters used with OnComputeInsetsListener. - */ - public final static class InsetsInfo { - - /** - * Offsets from the frame of the window at which the content of - * windows behind it should be placed. - */ - public final Rect contentInsets = new Rect(); - - /** - * Offsets from the frame of the window at which windows behind it - * are visible. - */ - public final Rect visibleInsets = new Rect(); - - /** - * Touchable region defined relative to the origin of the frame of the window. - * Only used when {@link #setTouchableInsets(int)} is called with - * the option {@link #TOUCHABLE_INSETS_REGION}. - */ - public final Region touchableRegion = new Region(); - - /** - * Option for {@link #setTouchableInsets(int)}: the entire window frame - * can be touched. - */ - public static final int TOUCHABLE_INSETS_FRAME = - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; - - /** - * Option for {@link #setTouchableInsets(int)}: the area inside of - * the content insets can be touched. - */ - public static final int TOUCHABLE_INSETS_CONTENT = - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; - - /** - * Option for {@link #setTouchableInsets(int)}: the area inside of - * the visible insets can be touched. - */ - public static final int TOUCHABLE_INSETS_VISIBLE = - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; - - /** - * Option for {@link #setTouchableInsets(int)}: the area inside of - * the provided touchable region in {@link #touchableRegion} can be touched. - */ - public static final int TOUCHABLE_INSETS_REGION = - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; - - /** - * Set which parts of the window can be touched: either - * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT}, - * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}. - */ - public void setTouchableInsets(int val) { - mTouchableInsets = val; - } - - int mTouchableInsets; - - @Override - public int hashCode() { - int result = contentInsets.hashCode(); - result = 31 * result + visibleInsets.hashCode(); - result = 31 * result + touchableRegion.hashCode(); - result = 31 * result + mTouchableInsets; - return result; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final InsetsInfo other = (InsetsInfo) o; - return mTouchableInsets == other.mTouchableInsets && - contentInsets.equals(other.contentInsets) && - visibleInsets.equals(other.visibleInsets) && - touchableRegion.equals(other.touchableRegion); - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java deleted file mode 100644 index 5577513f4c3c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.system; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; - -import android.app.WindowConfiguration; -import android.graphics.Rect; -import android.os.Handler; -import android.os.RemoteException; -import android.util.Log; -import android.view.InsetsController; -import android.view.InsetsFrameProvider; -import android.view.InsetsState; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; -import android.view.animation.Interpolator; - -import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; -import com.android.systemui.shared.recents.view.RecentsTransition; - -public class WindowManagerWrapper { - - private static final String TAG = "WindowManagerWrapper"; - - public static final int TRANSIT_UNSET = WindowManager.TRANSIT_OLD_UNSET; - public static final int TRANSIT_NONE = WindowManager.TRANSIT_OLD_NONE; - public static final int TRANSIT_ACTIVITY_OPEN = WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; - public static final int TRANSIT_ACTIVITY_CLOSE = WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; - public static final int TRANSIT_TASK_OPEN = WindowManager.TRANSIT_OLD_TASK_OPEN; - public static final int TRANSIT_TASK_CLOSE = WindowManager.TRANSIT_OLD_TASK_CLOSE; - public static final int TRANSIT_TASK_TO_FRONT = WindowManager.TRANSIT_OLD_TASK_TO_FRONT; - public static final int TRANSIT_TASK_TO_BACK = WindowManager.TRANSIT_OLD_TASK_TO_BACK; - public static final int TRANSIT_WALLPAPER_CLOSE = WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE; - public static final int TRANSIT_WALLPAPER_OPEN = WindowManager.TRANSIT_OLD_WALLPAPER_OPEN; - public static final int TRANSIT_WALLPAPER_INTRA_OPEN = - WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN; - public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = - WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; - public static final int TRANSIT_TASK_OPEN_BEHIND = WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND; - public static final int TRANSIT_ACTIVITY_RELAUNCH = WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH; - public static final int TRANSIT_KEYGUARD_GOING_AWAY = - WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; - public static final int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = - WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER; - public static final int TRANSIT_KEYGUARD_OCCLUDE = WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE; - public static final int TRANSIT_KEYGUARD_UNOCCLUDE = - WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; - - public static final int NAV_BAR_POS_INVALID = NAV_BAR_INVALID; - public static final int NAV_BAR_POS_LEFT = NAV_BAR_LEFT; - public static final int NAV_BAR_POS_RIGHT = NAV_BAR_RIGHT; - public static final int NAV_BAR_POS_BOTTOM = NAV_BAR_BOTTOM; - - public static final int ACTIVITY_TYPE_STANDARD = WindowConfiguration.ACTIVITY_TYPE_STANDARD; - - public static final int WINDOWING_MODE_UNDEFINED = WindowConfiguration.WINDOWING_MODE_UNDEFINED; - public static final int WINDOWING_MODE_FULLSCREEN = - WindowConfiguration.WINDOWING_MODE_FULLSCREEN; - public static final int WINDOWING_MODE_MULTI_WINDOW = - WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; - - public static final int WINDOWING_MODE_FREEFORM = WindowConfiguration.WINDOWING_MODE_FREEFORM; - - public static final int ITYPE_EXTRA_NAVIGATION_BAR = InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; - public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = InsetsState.ITYPE_LEFT_TAPPABLE_ELEMENT; - public static final int ITYPE_TOP_TAPPABLE_ELEMENT = InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; - public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = InsetsState.ITYPE_RIGHT_TAPPABLE_ELEMENT; - public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = - InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; - public static final int ITYPE_SIZE = InsetsState.SIZE; - - public static final int ANIMATION_DURATION_RESIZE = InsetsController.ANIMATION_DURATION_RESIZE; - public static final Interpolator RESIZE_INTERPOLATOR = InsetsController.RESIZE_INTERPOLATOR; - - private static final WindowManagerWrapper sInstance = new WindowManagerWrapper(); - - public static WindowManagerWrapper getInstance() { - return sInstance; - } - - - /** - * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}. - * @param params The window layout params. - * @param providesInsetsTypes The inset types we would like this layout params to provide. - */ - public void setProvidesInsetsTypes(WindowManager.LayoutParams params, - int[] providesInsetsTypes) { - final int length = providesInsetsTypes.length; - params.providedInsets = new InsetsFrameProvider[length]; - for (int i = 0; i < length; i++) { - params.providedInsets[i] = new InsetsFrameProvider(providesInsetsTypes[i]); - } - } - - /** - * Overrides a pending app transition. - */ - public void overridePendingAppTransitionMultiThumbFuture( - AppTransitionAnimationSpecsFuture animationSpecFuture, Runnable animStartedCallback, - Handler animStartedCallbackHandler, boolean scaleUp, int displayId) { - try { - WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(), - RecentsTransition.wrapStartedListener(animStartedCallbackHandler, - animStartedCallback), scaleUp, displayId); - } catch (RemoteException e) { - Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e); - } - } - - /** - * Enable or disable haptic feedback on the navigation bar buttons. - */ - public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) { - try { - WindowManagerGlobal.getWindowManagerService() - .setNavBarVirtualKeyHapticFeedbackEnabled(enabled); - } catch (RemoteException e) { - Log.w(TAG, "Failed to enable or disable navigation bar button haptics: ", e); - } - } - - /** - * @param displayId the id of display to check if there is a software navigation bar. - * - * @return whether there is a soft nav bar on specific display. - */ - public boolean hasSoftNavigationBar(int displayId) { - try { - return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(displayId); - } catch (RemoteException e) { - return false; - } - } - - /** - * Mirrors a specified display. The SurfaceControl returned is the root of the mirrored - * hierarchy. - * - * @param displayId The id of the display to mirror - * @return The SurfaceControl for the root of the mirrored hierarchy. - */ - public SurfaceControl mirrorDisplay(final int displayId) { - try { - SurfaceControl outSurfaceControl = new SurfaceControl(); - WindowManagerGlobal.getWindowManagerService().mirrorDisplay(displayId, - outSurfaceControl); - return outSurfaceControl; - } catch (RemoteException e) { - Log.e(TAG, "Unable to reach window manager", e); - } - return null; - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 919b71bbf879..f82e7dbd5d12 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -55,6 +55,8 @@ data class KeyguardFaceListenModel( val bouncerIsOrWillShow: Boolean, val faceAuthenticated: Boolean, val faceDisabled: Boolean, + val faceLockedOut: Boolean, + val fpLockedOut: Boolean, val goingToSleep: Boolean, val keyguardAwakeExcludingBouncerShowing: Boolean, val keyguardGoingAway: Boolean, @@ -65,7 +67,7 @@ data class KeyguardFaceListenModel( val scanningAllowedByStrongAuth: Boolean, val secureCameraLaunched: Boolean, val switchingUser: Boolean, - val udfpsBouncerShowing: Boolean + val udfpsBouncerShowing: Boolean, ) : KeyguardListenModel() /** * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock]. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 20fa8f817dc0..453072bc42da 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -48,6 +48,9 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { private ConstraintLayout mContainer; private int mDisappearYTranslation; private View[][] mViews; + private int mYTrans; + private int mYTransOffset; + private View mBouncerMessageView; @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN; public KeyguardPINView(Context context) { @@ -67,6 +70,8 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mContext, android.R.interpolator.fast_out_linear_in)); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); + mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry); + mYTransOffset = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry_offset); } @Override @@ -138,6 +143,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { super.onFinishInflate(); mContainer = findViewById(R.id.pin_container); + mBouncerMessageView = findViewById(R.id.bouncer_message_area); mViews = new View[][]{ new View[]{ findViewById(R.id.row0), null, null @@ -206,6 +212,12 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { /** Animate subviews according to expansion or time. */ private void animate(float progress) { + Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE; + Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE; + + mBouncerMessageView.setTranslationY( + mYTrans - mYTrans * standardDecelerate.getInterpolation(progress)); + for (int i = 0; i < mViews.length; i++) { View[] row = mViews[i]; for (View view : row) { @@ -213,14 +225,15 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { continue; } - float scaledProgress = MathUtils.constrain( + float scaledProgress = legacyDecelerate.getInterpolation(MathUtils.constrain( (progress - 0.075f * i) / (1f - 0.075f * mViews.length), 0f, 1f - ); + )); view.setAlpha(scaledProgress); - Interpolator interpolator = Interpolators.STANDARD_ACCELERATE; - view.setTranslationY(40 - (40 * interpolator.getInterpolation(scaledProgress))); + int yDistance = mYTrans + mYTransOffset * i; + view.setTranslationY( + yDistance - (yDistance * standardDecelerate.getInterpolation(progress))); if (view instanceof NumPadAnimationListener) { ((NumPadAnimationListener) view).setProgress(scaledProgress); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 9f4585fb1a92..89fcc47caf57 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -82,6 +82,12 @@ public class KeyguardPinViewController } @Override + public void startAppearAnimation() { + mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin); + super.startAppearAnimation(); + } + + @Override public boolean startDisappearAnimation(Runnable finishRunnable) { return mView.startDisappearAnimation( mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt new file mode 100644 index 000000000000..b4bfca1185f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt @@ -0,0 +1,54 @@ +/* + * 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.keyguard + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import androidx.core.graphics.drawable.DrawableCompat +import com.android.systemui.R + +abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) : + KeyguardPinBasedInputView(context, attrs) { + private var simImageView: ImageView? = null + private var disableESimButton: KeyguardEsimArea? = null + + override fun onFinishInflate() { + simImageView = findViewById(R.id.keyguard_sim) + disableESimButton = findViewById(R.id.keyguard_esim_area) + super.onFinishInflate() + } + + /** Set UI state based on whether there is a locked eSim card */ + fun setESimLocked(isESimLocked: Boolean, subId: Int) { + disableESimButton?.setSubscriptionId(subId) + disableESimButton?.visibility = if (isESimLocked) VISIBLE else GONE + simImageView?.visibility = if (isESimLocked) GONE else VISIBLE + } + + override fun reloadColors() { + super.reloadColors() + val customAttrs = intArrayOf(android.R.attr.textColorSecondary) + val a = context.obtainStyledAttributes(customAttrs) + val imageColor = a.getColor(0, 0) + a.recycle() + simImageView?.let { + val wrappedDrawable = DrawableCompat.wrap(it.drawable) + DrawableCompat.setTint(wrappedDrawable, imageColor) + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index ae9d3dfec3b2..9d170150a709 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -18,21 +18,14 @@ package com.android.keyguard; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; - -import androidx.core.graphics.drawable.DrawableCompat; import com.android.systemui.R; /** * Displays a PIN pad for unlocking. */ -public class KeyguardSimPinView extends KeyguardPinBasedInputView { - private ImageView mSimImageView; +public class KeyguardSimPinView extends KeyguardSimInputView { public static final String TAG = "KeyguardSimPinView"; public KeyguardSimPinView(Context context) { @@ -43,12 +36,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { super(context, attrs); } - public void setEsimLocked(boolean locked, int subscriptionId) { - KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setSubscriptionId(subscriptionId); - esimButton.setVisibility(locked ? View.VISIBLE : View.GONE); - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -68,7 +55,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { @Override protected void onFinishInflate() { - mSimImageView = findViewById(R.id.keyguard_sim); super.onFinishInflate(); if (mEcaView instanceof EmergencyCarrierArea) { @@ -86,17 +72,4 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); } - - @Override - public void reloadColors() { - super.reloadColors(); - - int[] customAttrs = {android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(customAttrs); - int imageColor = a.getColor(0, 0); - a.recycle(); - Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable()); - DrawableCompat.setTint(wrappedDrawable, imageColor); - } } - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 2a2e9bf54bb2..91bf20f90690 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -105,7 +105,7 @@ public class KeyguardSimPinViewController showDefaultMessage(); } - mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); + mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index c0971bf8c16d..5f45fe31a779 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -19,13 +19,8 @@ package com.android.keyguard; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; -import android.widget.ImageView; - -import androidx.core.graphics.drawable.DrawableCompat; import com.android.systemui.R; @@ -33,8 +28,7 @@ import com.android.systemui.R; /** * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ -public class KeyguardSimPukView extends KeyguardPinBasedInputView { - private ImageView mSimImageView; +public class KeyguardSimPukView extends KeyguardSimInputView { private static final boolean DEBUG = KeyguardConstants.DEBUG; public static final String TAG = "KeyguardSimPukView"; @@ -86,7 +80,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { @Override protected void onFinishInflate() { - mSimImageView = findViewById(R.id.keyguard_sim); super.onFinishInflate(); if (mEcaView instanceof EmergencyCarrierArea) { @@ -104,18 +97,4 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock); } - - @Override - public void reloadColors() { - super.reloadColors(); - - int[] customAttrs = {android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(customAttrs); - int imageColor = a.getColor(0, 0); - a.recycle(); - Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable()); - DrawableCompat.setTint(wrappedDrawable, imageColor); - } } - - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 203f9b660536..d8cffd7984ba 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -30,7 +30,6 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; -import android.view.View; import android.view.WindowManager; import android.widget.ImageView; @@ -173,11 +172,9 @@ public class KeyguardSimPukViewController if (mShowDefaultMessage) { showDefaultMessage(); } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); - KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); - esimButton.setSubscriptionId(mSubId); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); + mPasswordEntry.requestFocus(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 1463840b4cde..7e04f042e487 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -157,6 +157,7 @@ import com.google.android.collect.Lists; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -167,6 +168,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -266,6 +268,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; private final UiEventLogger mUiEventLogger; + private final Set<Integer> mFaceAcquiredInfoIgnoreList; private int mStatusBarState; private final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { @@ -1008,6 +1011,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAuthFailed() { Assert.isMainThread(); + mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1624,6 +1628,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { + return; + } handleFaceHelp(helpMsgId, helpString.toString()); } @@ -1916,6 +1923,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mWakeOnFingerprintAcquiredStart = context.getResources() .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); + mFaceAcquiredInfoIgnoreList = Arrays.stream( + mContext.getResources().getIntArray( + R.array.config_face_acquire_device_entry_ignorelist)) + .boxed() + .collect(Collectors.toSet()); mHandler = new Handler(mainLooper) { @Override @@ -2612,6 +2624,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); final boolean onlyFaceEnrolled = isOnlyFaceEnrolled(); + final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout; // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -2628,7 +2641,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && strongAuthAllowsScanning && mIsPrimaryUser && (!mSecureCameraLaunched || mOccludingAppRequestingFace) && !faceAuthenticated - && !fpLockedout; + && !fpOrFaceIsLockedOut; // Aggregate relevant fields for debug logging. maybeLogListenerModelData( @@ -2643,6 +2656,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mBouncerIsOrWillBeShowing, faceAuthenticated, faceDisabledForUser, + isFaceLockedOut(), + fpLockedout, mGoingToSleep, awakeKeyguardExcludingBouncerShowing, mKeyguardGoingAway, diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index e0cafaed5a35..41111e3d3c6c 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -86,7 +86,7 @@ class NumPadAnimator { public void setProgress(float progress) { mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress); - int height = (int) (mHeight * 0.8f + mHeight * 0.2 * progress); + int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress); int difference = mHeight - height; mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2); } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt new file mode 100644 index 000000000000..2c2ab7b39161 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -0,0 +1,71 @@ +/* + * 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.keyguard.logging + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.dagger.BiometricMessagesLog +import javax.inject.Inject + +/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ +@SysUISingleton +class FaceMessageDeferralLogger +@Inject +constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) : + BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger") + +open class BiometricMessageDeferralLogger( + private val logBuffer: LogBuffer, + private val tag: String +) { + fun reset() { + logBuffer.log(tag, DEBUG, "reset") + } + + fun logUpdateMessage(acquiredInfo: Int, helpString: String) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + str1 = helpString + }, + { "updateMessage acquiredInfo=$int1 helpString=$str1" } + ) + } + + fun logFrameProcessed( + acquiredInfo: Int, + totalFrames: Int, + mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold + ) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + int2 = totalFrames + str1 = mostFrequentAcquiredInfoToDefer + }, + { + "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " + + "messageToShowOnTimeout=$str1" + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 9138b2346ab8..9cfd3999a0ec 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -16,7 +16,6 @@ package com.android.systemui; -import android.app.ActivityManager; import android.app.ActivityThread; import android.app.Application; import android.app.Notification; @@ -29,7 +28,6 @@ import android.content.res.Configuration; import android.os.Bundle; import android.os.Looper; import android.os.Process; -import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; @@ -130,13 +128,6 @@ public class SystemUIApplication extends Application implements ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG); } - // Enable binder tracing on system server for calls originating from SysUI - try { - ActivityManager.getService().enableBinderTracing(); - } catch (RemoteException e) { - Log.e(TAG, "Unable to enable binder tracing", e); - } - registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 813f4ddbab0b..e3c04a379fe4 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -62,6 +62,7 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.WindowMetrics; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -75,7 +76,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; import com.android.systemui.model.SysUiState; -import com.android.systemui.shared.system.WindowManagerWrapper; import java.io.PrintWriter; import java.text.NumberFormat; @@ -689,7 +689,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (Float.isNaN(centerX)) { centerX = mMagnificationFrame.centerX(); } - if (Float.isNaN(centerX)) { + if (Float.isNaN(centerY)) { centerY = mMagnificationFrame.centerY(); } @@ -723,7 +723,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold * child of the surfaceView. */ private void createMirror() { - mMirrorSurface = WindowManagerWrapper.getInstance().mirrorDisplay(mDisplayId); + mMirrorSurface = mirrorDisplay(mDisplayId); if (!mMirrorSurface.isValid()) { return; } @@ -732,6 +732,25 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold modifyWindowMagnification(false); } + /** + * Mirrors a specified display. The SurfaceControl returned is the root of the mirrored + * hierarchy. + * + * @param displayId The id of the display to mirror + * @return The SurfaceControl for the root of the mirrored hierarchy. + */ + private SurfaceControl mirrorDisplay(final int displayId) { + try { + SurfaceControl outSurfaceControl = new SurfaceControl(); + WindowManagerGlobal.getWindowManagerService().mirrorDisplay(displayId, + outSurfaceControl); + return outSurfaceControl; + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach window manager", e); + } + return null; + } + private void addDragTouchListeners() { mDragView = mMirrorView.findViewById(R.id.drag_handle); mLeftDrag = mMirrorView.findViewById(R.id.left_handle); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index e94b1f8122a6..0ac71c462e21 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -169,6 +169,10 @@ public abstract class AuthBiometricView extends LinearLayout { private Animator.AnimatorListener mJankListener; + private final boolean mUseCustomBpSize; + private final int mCustomBpWidth; + private final int mCustomBpHeight; + private final OnClickListener mBackgroundClickListener = (view) -> { if (mState == STATE_AUTHENTICATED) { Log.w(TAG, "Ignoring background click after authenticated"); @@ -209,6 +213,10 @@ public abstract class AuthBiometricView extends LinearLayout { handleResetAfterHelp(); Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); }; + + mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size); + mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width); + mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height); } /** Delay after authentication is confirmed, before the dialog should be animated away. */ @@ -834,14 +842,17 @@ public abstract class AuthBiometricView extends LinearLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int width = MeasureSpec.getSize(widthMeasureSpec); - final int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); - final int newWidth = Math.min(width, height); + if (mUseCustomBpSize) { + width = mCustomBpWidth; + height = mCustomBpHeight; + } else { + width = Math.min(width, height); + } - // Use "newWidth" instead, so the landscape dialog width is the same as the portrait - // width. - mLayoutParams = onMeasureInternal(newWidth, height); + mLayoutParams = onMeasureInternal(width, height); setMeasuredDimension(mLayoutParams.mMediumWidth, mLayoutParams.mMediumHeight); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 436b756ea0cb..8f5cbb76222f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -54,6 +54,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; @@ -127,6 +129,7 @@ public class AuthContainerView extends LinearLayout private final float mTranslationY; @ContainerState private int mContainerState = STATE_UNKNOWN; private final Set<Integer> mFailedModalities = new HashSet<Integer>(); + private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; private final @Background DelayableExecutor mBackgroundExecutor; private int mOrientation; @@ -362,8 +365,7 @@ public class AuthContainerView extends LinearLayout return false; } if (event.getAction() == KeyEvent.ACTION_UP) { - sendEarlyUserCanceled(); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + onBackInvoked(); } return true; }); @@ -373,6 +375,11 @@ public class AuthContainerView extends LinearLayout requestFocus(); } + private void onBackInvoked() { + sendEarlyUserCanceled(); + animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + } + void sendEarlyUserCanceled() { mConfig.mCallback.onSystemEvent( BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId()); @@ -520,6 +527,11 @@ public class AuthContainerView extends LinearLayout .start(); }); } + OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); + if (dispatcher != null) { + dispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback); + } } private Animator.AnimatorListener getJankListener(View v, String type, long timeout) { @@ -618,6 +630,10 @@ public class AuthContainerView extends LinearLayout @Override public void onDetachedFromWindow() { + OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); + if (dispatcher != null) { + findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback); + } super.onDetachedFromWindow(); mWakefulnessLifecycle.removeObserver(this); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt deleted file mode 100644 index f2d8aaa30f21..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt +++ /dev/null @@ -1,89 +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.biometrics - -/** - * Provides whether an acquired error message should be shown immediately when its received (see - * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage]. - * @property excludedMessages messages that are excluded from counts - * @property messagesToDefer messages that shouldn't show immediately when received, but may be - * shown later if the message is the most frequent message processed and meets [THRESHOLD] - * percentage of all messages (excluding [excludedMessages]) - */ -class BiometricMessageDeferral( - private val excludedMessages: Set<Int>, - private val messagesToDefer: Set<Int> -) { - private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg - private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message - private var totalRelevantMessages = 0 - private var mostFrequentMsgIdToDefer: Int? = null - - /** Reset all saved counts. */ - fun reset() { - totalRelevantMessages = 0 - msgCounts.clear() - msgIdToCharSequence.clear() - } - - /** Whether the given message should be deferred instead of being shown immediately. */ - fun shouldDefer(acquiredMsgId: Int): Boolean { - return messagesToDefer.contains(acquiredMsgId) - } - - /** - * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count - * messages that shouldn't be deferred in these counts. - */ - fun processMessage(acquiredMsgId: Int, helpString: CharSequence) { - if (excludedMessages.contains(acquiredMsgId)) { - return - } - - totalRelevantMessages++ - msgIdToCharSequence[acquiredMsgId] = helpString - - val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1 - msgCounts[acquiredMsgId] = newAcquiredMsgCount - if ( - messagesToDefer.contains(acquiredMsgId) && - (mostFrequentMsgIdToDefer == null || - newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0)) - ) { - mostFrequentMsgIdToDefer = acquiredMsgId - } - } - - /** - * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed - * messages excluding [excludedMessages]. - * @return null if no messages have been deferred OR deferred messages didn't meet the - * [THRESHOLD] percentage of messages to show. - */ - fun getDeferredMessage(): CharSequence? { - mostFrequentMsgIdToDefer?.let { - if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) { - return msgIdToCharSequence[mostFrequentMsgIdToDefer] - } - } - - return null - } - companion object { - const val THRESHOLD = .5f - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt new file mode 100644 index 000000000000..fabc1c1bb908 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.res.Resources +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.keyguard.logging.FaceMessageDeferralLogger +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import java.io.PrintWriter +import java.util.* +import javax.inject.Inject + +/** + * Provides whether a face acquired help message should be shown immediately when its received or + * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage]. + */ +@SysUISingleton +class FaceHelpMessageDeferral +@Inject +constructor( + @Main resources: Resources, + logBuffer: FaceMessageDeferralLogger, + dumpManager: DumpManager +) : + BiometricMessageDeferral( + resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(), + resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), + logBuffer, + dumpManager + ) + +/** + * @property messagesToDefer messages that shouldn't show immediately when received, but may be + * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] + * percentage of all passed acquired frames. + */ +open class BiometricMessageDeferral( + private val messagesToDefer: Set<Int>, + private val threshold: Float, + private val logBuffer: BiometricMessageDeferralLogger, + dumpManager: DumpManager +) : Dumpable { + private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() + private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() + private var mostFrequentAcquiredInfoToDefer: Int? = null + private var totalFrames = 0 + + init { + dumpManager.registerDumpable(this.javaClass.name, this) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("messagesToDefer=$messagesToDefer") + pw.println("totalFrames=$totalFrames") + pw.println("threshold=$threshold") + } + + /** Reset all saved counts. */ + fun reset() { + totalFrames = 0 + mostFrequentAcquiredInfoToDefer = null + acquiredInfoToFrequency.clear() + acquiredInfoToHelpString.clear() + logBuffer.reset() + } + + /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ + fun updateMessage(acquiredInfo: Int, helpString: String) { + if (!messagesToDefer.contains(acquiredInfo)) { + return + } + if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { + logBuffer.logUpdateMessage(acquiredInfo, helpString) + acquiredInfoToHelpString[acquiredInfo] = helpString + } + } + + /** Whether the given message should be deferred instead of being shown immediately. */ + fun shouldDefer(acquiredMsgId: Int): Boolean { + return messagesToDefer.contains(acquiredMsgId) + } + + /** Adds the acquiredInfo frame to the counts. We account for all frames. */ + fun processFrame(acquiredInfo: Int) { + if (messagesToDefer.isEmpty()) { + return + } + + totalFrames++ + + val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 + acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount + if ( + messagesToDefer.contains(acquiredInfo) && + (mostFrequentAcquiredInfoToDefer == null || + newAcquiredInfoCount > + acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) + ) { + mostFrequentAcquiredInfoToDefer = acquiredInfo + } + + logBuffer.logFrameProcessed( + acquiredInfo, + totalFrames, + mostFrequentAcquiredInfoToDefer?.toString() + ) + } + + /** + * Get the most frequent deferred message that meets the [threshold] percentage of processed + * frames. + * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the + * [threshold] percentage. + */ + fun getDeferredMessage(): CharSequence? { + mostFrequentAcquiredInfoToDefer?.let { + if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { + return acquiredInfoToHelpString[it] + } + } + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt new file mode 100644 index 000000000000..96af42bfda22 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -0,0 +1,102 @@ +/* + * 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.bluetooth + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.BluetoothLog +import javax.inject.Inject + +/** Helper class for logging bluetooth events. */ +@SysUISingleton +class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) { + fun logActiveDeviceChanged(address: String?, profileId: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + int1 = profileId + }, + { "ActiveDeviceChanged. address=$str1 profileId=$int1" } + ) + + fun logDeviceConnectionStateChanged(address: String?, state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + }, + { "DeviceConnectionStateChanged. address=$str1 state=$str2" } + ) + + fun logAclConnectionStateChanged(address: String, state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + }, + { "AclConnectionStateChanged. address=$str1 state=$str2" } + ) + + fun logProfileConnectionStateChanged(address: String?, state: String, profileId: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + int1 = profileId + }, + { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" } + ) + + fun logStateChange(state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { str1 = state }, + { "BluetoothStateChanged. state=$str1" } + ) + + fun logBondStateChange(address: String, state: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + int1 = state + }, + { "DeviceBondStateChanged. address=$str1 state=$int1" } + ) + + fun logDeviceAdded(address: String) = + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" }) + + fun logDeviceDeleted(address: String) = + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" }) + + fun logDeviceAttributesChanged() = + logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." }) +} + +private const val TAG = "BluetoothLog" diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 31a2134851a2..bfbf37a5d3e9 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -291,7 +291,7 @@ public class BrightLineFalsingManager implements FalsingManager { FalsingClassifier.Result.falsed( 0, getClass().getSimpleName(), "bad history")); logDebug("False Single Tap: true (bad history)"); - mFalsingTapListeners.forEach(FalsingTapListener::onDoubleTapRequired); + mFalsingTapListeners.forEach(FalsingTapListener::onAdditionalTapRequired); return true; } else { mPriorResults = getPassedResult(0.1); @@ -321,7 +321,7 @@ public class BrightLineFalsingManager implements FalsingManager { mHistoryTracker.falseBelief(), mHistoryTracker.falseConfidence()); mPriorResults = Collections.singleton(result); - logDebug("False Double Tap: " + result.isFalse()); + logDebug("False Double Tap: " + result.isFalse() + " reason=" + result.getReason()); return result.isFalse(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 23d87ff980ca..f5f9655ef24b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -303,9 +303,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { - if (!mKeyguardStateController.isShowing() - || (mStatusBarStateController.isDozing() - && !mStatusBarStateController.isPulsing())) { + if (!mKeyguardStateController.isShowing()) { avoidGesture(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/drawable/CircularDrawable.kt b/packages/SystemUI/src/com/android/systemui/common/ui/drawable/CircularDrawable.kt new file mode 100644 index 000000000000..ec71c3824156 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/drawable/CircularDrawable.kt @@ -0,0 +1,54 @@ +/* + * 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.common.ui.drawable + +import android.graphics.Canvas +import android.graphics.Path +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import kotlin.math.min + +/** Renders the wrapped [Drawable] as a circle. */ +class CircularDrawable( + drawable: Drawable, +) : DrawableWrapper(drawable) { + private val path: Path by lazy { Path() } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + updateClipPath() + } + + override fun draw(canvas: Canvas) { + canvas.save() + canvas.clipPath(path) + drawable?.draw(canvas) + canvas.restore() + } + + private fun updateClipPath() { + path.reset() + path.addCircle( + bounds.centerX().toFloat(), + bounds.centerY().toFloat(), + min(bounds.width(), bounds.height()) / 2f, + Path.Direction.CW + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java index be156157fb72..6b9d41c41a85 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java @@ -19,7 +19,6 @@ package com.android.systemui.dagger; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.DevicePolicyManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListeners; -import com.android.systemui.shared.system.WindowManagerWrapper; import dagger.Module; import dagger.Provides; @@ -49,10 +48,4 @@ public class SharedLibraryModule { return TaskStackChangeListeners.getInstance(); } - /** */ - @Provides - public WindowManagerWrapper providesWindowManagerWrapper() { - return WindowManagerWrapper.getInstance(); - } - } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 823255c38a84..f244cb009ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -61,6 +61,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { private final Map<Integer, View> mStatusIcons = new HashMap<>(); private ViewGroup mSystemStatusViewGroup; + private ViewGroup mExtraSystemStatusViewGroup; public DreamOverlayStatusBarView(Context context) { this(context, null); @@ -98,7 +99,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON, fetchStatusIconForResId(R.id.dream_overlay_priority_mode)); - mSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items); + mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status); + mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items); } void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) { @@ -110,11 +112,12 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { icon.setContentDescription(contentDescription); } icon.setVisibility(show ? View.VISIBLE : View.GONE); + mSystemStatusViewGroup.setVisibility(areAnyStatusIconsVisible() ? View.VISIBLE : View.GONE); } void setExtraStatusBarItemViews(List<View> views) { - removeAllStatusBarItemViews(); - views.forEach(view -> mSystemStatusViewGroup.addView(view)); + removeAllExtraStatusBarItemViews(); + views.forEach(view -> mExtraSystemStatusViewGroup.addView(view)); } private View fetchStatusIconForResId(int resId) { @@ -122,7 +125,16 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { return Objects.requireNonNull(statusIcon); } - void removeAllStatusBarItemViews() { - mSystemStatusViewGroup.removeAllViews(); + void removeAllExtraStatusBarItemViews() { + mExtraSystemStatusViewGroup.removeAllViews(); + } + + private boolean areAnyStatusIconsVisible() { + for (int i = 0; i < mSystemStatusViewGroup.getChildCount(); i++) { + if (mSystemStatusViewGroup.getChildAt(i).getVisibility() == View.VISIBLE) { + return true; + } + } + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 6f505504b186..aa59cc666caa 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -192,7 +192,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDreamOverlayNotificationCountProvider.ifPresent( provider -> provider.removeCallback(mNotificationCountCallback)); mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); - mView.removeAllStatusBarItemViews(); + mView.removeAllExtraStatusBarItemViews(); mTouchInsetSession.clear(); mIsAttached = false; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java index 653f4dc66200..789ebc517271 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java @@ -17,24 +17,21 @@ package com.android.systemui.dreams.complication; import android.content.Context; +import android.content.res.Resources; import android.graphics.Canvas; import android.util.AttributeSet; import android.widget.TextClock; import com.android.systemui.R; +import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo; + +import kotlin.Unit; /** * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows) */ public class DoubleShadowTextClock extends TextClock { - private final float mAmbientShadowBlur; - private final int mAmbientShadowColor; - private final float mKeyShadowBlur; - private final float mKeyShadowOffsetX; - private final float mKeyShadowOffsetY; - private final int mKeyShadowColor; - private final float mAmbientShadowOffsetX; - private final float mAmbientShadowOffsetY; + private final DoubleShadowTextHelper mShadowHelper; public DoubleShadowTextClock(Context context) { this(context, null); @@ -46,38 +43,28 @@ public class DoubleShadowTextClock extends TextClock { public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mKeyShadowBlur = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius); - mKeyShadowOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx); - mKeyShadowOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy); - mKeyShadowColor = context.getResources().getColor( - R.color.dream_overlay_clock_key_text_shadow_color); - mAmbientShadowBlur = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_radius); - mAmbientShadowColor = context.getResources().getColor( - R.color.dream_overlay_clock_ambient_text_shadow_color); - mAmbientShadowOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx); - mAmbientShadowOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy); + + final Resources resources = context.getResources(); + final ShadowInfo keyShadowInfo = new ShadowInfo( + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy), + resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color)); + + final ShadowInfo ambientShadowInfo = new ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_clock_ambient_text_shadow_radius), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy), + resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color)); + mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); } @Override public void onDraw(Canvas canvas) { - // We enhance the shadow by drawing the shadow twice - getPaint().setShadowLayer(mAmbientShadowBlur, mAmbientShadowOffsetX, mAmbientShadowOffsetY, - mAmbientShadowColor); - super.onDraw(canvas); - canvas.save(); - canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), - getScrollX() + getWidth(), - getScrollY() + getHeight()); - - getPaint().setShadowLayer( - mKeyShadowBlur, mKeyShadowOffsetX, mKeyShadowOffsetY, mKeyShadowColor); - super.onDraw(canvas); - canvas.restore(); + mShadowHelper.applyShadows(this, canvas, () -> { + super.onDraw(canvas); + return Unit.INSTANCE; + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt new file mode 100644 index 000000000000..b1dc5a2e5dea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt @@ -0,0 +1,61 @@ +/* + * 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.dreams.complication + +import android.graphics.Canvas +import android.widget.TextView +import androidx.annotation.ColorInt + +class DoubleShadowTextHelper +constructor( + private val keyShadowInfo: ShadowInfo, + private val ambientShadowInfo: ShadowInfo, +) { + data class ShadowInfo( + val blur: Float, + val offsetX: Float = 0f, + val offsetY: Float = 0f, + @ColorInt val color: Int + ) + + fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) { + // We enhance the shadow by drawing the shadow twice + view.paint.setShadowLayer( + ambientShadowInfo.blur, + ambientShadowInfo.offsetX, + ambientShadowInfo.offsetY, + ambientShadowInfo.color + ) + onDrawCallback() + canvas.save() + canvas.clipRect( + view.scrollX, + view.scrollY + view.extendedPaddingTop, + view.scrollX + view.width, + view.scrollY + view.height + ) + + view.paint.setShadowLayer( + keyShadowInfo.blur, + keyShadowInfo.offsetX, + keyShadowInfo.offsetY, + keyShadowInfo.color + ) + onDrawCallback() + canvas.restore() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java new file mode 100644 index 000000000000..cf7e3127dedf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java @@ -0,0 +1,76 @@ +/* + * 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.dreams.complication; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.systemui.R; + +import kotlin.Unit; + +/** + * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows} + */ +public class DoubleShadowTextView extends TextView { + private final DoubleShadowTextHelper mShadowHelper; + + public DoubleShadowTextView(Context context) { + this(context, null); + } + + public DoubleShadowTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final Resources resources = context.getResources(); + final DoubleShadowTextHelper.ShadowInfo + keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_radius), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_dx), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_dy), + resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color)); + + final DoubleShadowTextHelper.ShadowInfo + ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy), + resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color)); + mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); + } + + @Override + public void onDraw(Canvas canvas) { + mShadowHelper.applyShadows(this, canvas, () -> { + super.onDraw(canvas); + return Unit.INSTANCE; + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 78099d1fd12f..2540035ab793 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -98,7 +98,7 @@ public class Flags { * Flag to enable the usage of the new bouncer data source. This is a refactor of and * eventual replacement of KeyguardBouncer.java. */ - public static final ReleasedFlag MODERN_BOUNCER = new ReleasedFlag(208); + public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208); /** Whether UserSwitcherActivity should use modern architecture. */ public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY = @@ -187,6 +187,9 @@ public class Flags { // 802 - wallpaper rendering public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802); + // 803 - screen contents translation + public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803); + /***************************************/ // 900 - media public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900); diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index a3dc77993d30..568143c8df71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -42,6 +42,8 @@ import android.util.Pair; import android.util.Slog; import android.widget.Toast; +import androidx.annotation.NonNull; + import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -601,13 +603,14 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo private final class BluetoothCallbackHandler implements BluetoothCallback { @Override - public void onBluetoothStateChanged(int bluetoothState) { + public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) { mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED, bluetoothState, 0).sendToTarget(); } @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + public void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) { mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED, bondState, 0, cachedDevice).sendToTarget(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index ee5ef9905819..1c6cec22ffca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -173,7 +173,8 @@ public class KeyguardService extends Service { } } - // Wrap Keyguard going away animation + // Wrap Keyguard going away animation. + // Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy). private static IRemoteTransition wrap(IRemoteAnimationRunner runner) { return new IRemoteTransition.Stub() { final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks = @@ -353,6 +354,27 @@ public class KeyguardService extends Service { f = new TransitionFilter(); f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE}; mShellTransitions.registerRemote(f, unoccludeTransition); + + Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_OCCLUDE for DREAM"); + // Register for occluding by Dream + f = new TransitionFilter(); + f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; + f.mRequirements = new TransitionFilter.Requirement[]{ + new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; + // First require at-least one app of type DREAM showing that occludes. + f.mRequirements[0].mActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM; + f.mRequirements[0].mMustBeIndependent = false; + f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + // Then require that we aren't closing any occludes (because this would mean a + // regular task->task or activity->activity animation not involving keyguard). + f.mRequirements[1].mNot = true; + f.mRequirements[1].mMustBeIndependent = false; + f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + mShellTransitions.registerRemote(f, new RemoteTransition( + wrap(mKeyguardViewMediator.getOccludeByDreamAnimationRunner()), + getIApplicationThread())); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 8eca1bad213b..c135b3c36841 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -241,9 +241,8 @@ class KeyguardUnlockAnimationController @Inject constructor( */ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null - private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null + private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null private var surfaceBehindRemoteAnimationStartTime: Long = 0 - private var surfaceBehindParams: SyncRtSurfaceTransactionApplier.SurfaceParams? = null /** * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -458,7 +457,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * (fingerprint, tap, etc.) and the keyguard is going away. */ fun notifyStartSurfaceBehindRemoteAnimation( - target: RemoteAnimationTarget, + targets: Array<RemoteAnimationTarget>, startTime: Long, requestedShowSurfaceBehindKeyguard: Boolean ) { @@ -467,10 +466,7 @@ class KeyguardUnlockAnimationController @Inject constructor( keyguardViewController.viewRootImpl.view) } - // New animation, new params. - surfaceBehindParams = null - - surfaceBehindRemoteAnimationTarget = target + surfaceBehindRemoteAnimationTargets = targets surfaceBehindRemoteAnimationStartTime = startTime // If we specifically requested that the surface behind be made visible (vs. it being made @@ -597,7 +593,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * keyguard dismiss amount and the method of dismissal. */ private fun updateSurfaceBehindAppearAmount() { - if (surfaceBehindRemoteAnimationTarget == null) { + if (surfaceBehindRemoteAnimationTargets == null) { return } @@ -710,63 +706,60 @@ class KeyguardUnlockAnimationController @Inject constructor( * cancelled). */ fun setSurfaceBehindAppearAmount(amount: Float) { - if (surfaceBehindRemoteAnimationTarget == null) { - return - } + surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget -> + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height() + + var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) - // Otherwise, animate in the surface's scale/transltion. - val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() - - var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + - (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * - MathUtils.clamp(amount, 0f, 1f)) - - // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, so - // don't also scale the window. - if (keyguardStateController.isDismissingFromSwipe && - willUnlockWithInWindowLauncherAnimations) { - scaleFactor = 1f - } - - // Scale up from a point at the center-bottom of the surface. - surfaceBehindMatrix.setScale( - scaleFactor, - scaleFactor, - surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, - surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y - ) - - // Translate up from the bottom. - surfaceBehindMatrix.postTranslate( - 0f, - surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) - ) - - // If we're snapping the keyguard back, immediately begin fading it out. - val animationAlpha = - if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount - else surfaceBehindAlpha - - // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable - // to draw - val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash - if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && - sc?.isValid == true) { - with(SurfaceControl.Transaction()) { - setMatrix(sc, surfaceBehindMatrix, tmpFloat) - setCornerRadius(sc, roundedCornerRadius) - setAlpha(sc, animationAlpha) - apply() + // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, + // so don't also scale the window. + if (keyguardStateController.isDismissingFromSwipe && + willUnlockWithInWindowLauncherAnimations) { + scaleFactor = 1f } - } else { - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withMatrix(surfaceBehindMatrix) - .withCornerRadius(roundedCornerRadius) - .withAlpha(animationAlpha) - .build() + + // Translate up from the bottom. + surfaceBehindMatrix.setTranslate( + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(), + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) ) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.postScale( + scaleFactor, + scaleFactor, + keyguardViewController.viewRootImpl.width / 2f, + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y + ) + + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = + if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount + else surfaceBehindAlpha + + // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is + // unable to draw + val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash + if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && + sc?.isValid == true) { + with(SurfaceControl.Transaction()) { + setMatrix(sc, surfaceBehindMatrix, tmpFloat) + setCornerRadius(sc, roundedCornerRadius) + setAlpha(sc, animationAlpha) + apply() + } + } else { + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build() + ) + } } } @@ -791,8 +784,7 @@ class KeyguardUnlockAnimationController @Inject constructor( launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) // That target is no longer valid since the animation finished, null it out. - surfaceBehindRemoteAnimationTarget = null - surfaceBehindParams = null + surfaceBehindRemoteAnimationTargets = null playingCannedUnlockAnimation = false willUnlockWithInWindowLauncherAnimations = false @@ -824,7 +816,6 @@ class KeyguardUnlockAnimationController @Inject constructor( private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { surfaceTransactionApplier!!.scheduleApply(params) - surfaceBehindParams = params } private fun fadeInSurfaceBehind() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index dc034360ad09..26db3ee4926f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -831,7 +831,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {} @Override - public void onLaunchAnimationCancelled() { + public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded); } @@ -2580,18 +2580,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mInteractionJankMonitor.begin( createInteractionJankMonitorConf("DismissPanel")); - // Apply the opening animation on root task if exists - RemoteAnimationTarget aniTarget = apps[0]; - for (RemoteAnimationTarget tmpTarget : apps) { - if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) { - aniTarget = tmpTarget; - break; - } - } // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get() .notifyStartSurfaceBehindRemoteAnimation( - aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested); + apps, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java new file mode 100644 index 000000000000..7f1ad6d20c16 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java @@ -0,0 +1,33 @@ +/* + * 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.log.dagger; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral} + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface BiometricMessagesLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt new file mode 100644 index 000000000000..4887b6a14658 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt @@ -0,0 +1,22 @@ +/* + * 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.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for bluetooth. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt index 323ee21953ea..b551125fccc7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt @@ -1,4 +1,9 @@ package com.android.systemui.log.dagger +import javax.inject.Qualifier + /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) annotation class KeyguardUpdateMonitorLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index c2a87649adef..5612c22311fb 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -287,6 +287,17 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for use by + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}. + */ + @Provides + @SysUISingleton + @BiometricMessagesLog + public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) { + return factory.create("BiometricMessagesLog", 150); + } + + /** * Provides a {@link LogBuffer} for use by the status bar network controller. */ @Provides @@ -305,4 +316,14 @@ public class LogModule { public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) { return factory.create("KeyguardUpdateMonitorLog", 200); } + + /** + * Provides a {@link LogBuffer} for bluetooth-related logs. + */ + @Provides + @SysUISingleton + @BluetoothLog + public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) { + return factory.create("BluetoothLog", 50); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java index f03fbcba41b7..b237f2d74483 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java @@ -19,7 +19,6 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.log.LogBuffer; -import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -27,7 +26,7 @@ import java.lang.annotation.Retention; import javax.inject.Qualifier; /** - * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor} + * A {@link LogBuffer} for status bar connectivity events. */ @Qualifier @Documented diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index cc77ed15b5f0..654c15812988 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -458,7 +458,7 @@ class MediaCarouselController @Inject constructor( val existingPlayer = MediaPlayerData.getMediaPlayer(key) val curVisibleMediaKey = MediaPlayerData.playerKeys() .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) - val isCurVisibleMediaPlaying = MediaPlayerData.getMediaData(curVisibleMediaKey)?.isPlaying + val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying if (existingPlayer == null) { val newPlayer = mediaControlPanelFactory.get() newPlayer.attachPlayer(MediaViewHolder.create( @@ -1046,15 +1046,6 @@ internal object MediaPlayerData { } } - fun getMediaData(mediaSortKey: MediaSortKey?): MediaData? { - mediaData.forEach { (key, value) -> - if (value == mediaSortKey) { - return mediaData[key]?.data - } - } - return null - } - fun getMediaPlayer(key: String): MediaControlPanel? { return mediaData.get(key)?.let { mediaPlayers.get(it) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt index 88f6f3dd9d0e..6bc94cd5f525 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt @@ -60,6 +60,8 @@ class SquigglyProgress : Drawable() { linePaint.strokeWidth = value } + // Enables a transition region where the amplitude + // of the wave is reduced linearly across it. var transitionEnabled = true set(value) { field = value @@ -116,44 +118,40 @@ class SquigglyProgress : Drawable() { } val progress = level / 10_000f - val totalProgressPx = bounds.width() * progress - val waveProgressPx = bounds.width() * ( + val totalWidth = bounds.width().toFloat() + val totalProgressPx = totalWidth * progress + val waveProgressPx = totalWidth * ( if (!transitionEnabled || progress > matchedWaveEndpoint) progress else lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress))) // Build Wiggly Path - val waveStart = -phaseOffset - val waveEnd = waveProgressPx - val transitionLength = if (transitionEnabled) transitionPeriods * waveLength else 0.01f + val waveStart = -phaseOffset - waveLength / 2f + val waveEnd = if (transitionEnabled) totalWidth else waveProgressPx // helper function, computes amplitude for wave segment val computeAmplitude: (Float, Float) -> Float = { x, sign -> - sign * heightFraction * lineAmplitude * - lerpInvSat(waveEnd, waveEnd - transitionLength, x) + if (transitionEnabled) { + val length = transitionPeriods * waveLength + val coeff = lerpInvSat( + waveProgressPx + length / 2f, + waveProgressPx - length / 2f, + x) + sign * heightFraction * lineAmplitude * coeff + } else { + sign * heightFraction * lineAmplitude + } } - var currentX = waveEnd - var waveSign = if (phaseOffset < waveLength / 2) 1f else -1f + // Reset path object to the start path.rewind() + path.moveTo(waveStart, 0f) - // Draw flat line from end to wave endpoint - path.moveTo(bounds.width().toFloat(), 0f) - path.lineTo(waveEnd, 0f) - - // First wave has shortened wavelength - // approx quarter wave gets us to first wave peak - // shouldn't be big enough to notice it's not a sin wave - currentX -= phaseOffset % (waveLength / 2) - val controlRatio = 0.25f + // Build the wave, incrementing by half the wavelength each time + var currentX = waveStart + var waveSign = 1f var currentAmp = computeAmplitude(currentX, waveSign) - path.cubicTo( - waveEnd, currentAmp * controlRatio, - lerp(currentX, waveEnd, controlRatio), currentAmp, - currentX, currentAmp) - - // Other waves have full wavelength - val dist = -1 * waveLength / 2f - while (currentX > waveStart) { + val dist = waveLength / 2f + while (currentX < waveEnd) { waveSign = -waveSign val nextX = currentX + dist val midX = currentX + dist / 2 @@ -166,34 +164,35 @@ class SquigglyProgress : Drawable() { currentX = nextX } - // Draw path; clip to progress position + // translate to the start position of the progress bar for all draw commands + val clipTop = lineAmplitude + strokeWidth canvas.save() canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - canvas.clipRect( - 0f, - -lineAmplitude - strokeWidth, - totalProgressPx, - lineAmplitude + strokeWidth) - canvas.drawPath(path, wavePaint) - canvas.restore() - // Draw path; clip between progression position & far edge + // Draw path up to progress position canvas.save() - canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - canvas.clipRect( - totalProgressPx, - -lineAmplitude - strokeWidth, - bounds.width().toFloat(), - lineAmplitude + strokeWidth) - canvas.drawPath(path, linePaint) + canvas.clipRect(0f, -1f * clipTop, totalProgressPx, clipTop) + canvas.drawPath(path, wavePaint) canvas.restore() + if (transitionEnabled) { + // If there's a smooth transition, we draw the rest of the + // path in a different color (using different clip params) + canvas.save() + canvas.clipRect(totalProgressPx, -1f * clipTop, totalWidth, clipTop) + canvas.drawPath(path, linePaint) + canvas.restore() + } else { + // No transition, just draw a flat line to the end of the region. + // The discontinuity is hidden by the progress bar thumb shape. + canvas.drawLine(totalProgressPx, 0f, totalWidth, 0f, linePaint) + } + // Draw round line cap at the beginning of the wave - val startAmp = cos(abs(waveEnd - phaseOffset) / waveLength * TWO_PI) - canvas.drawPoint( - bounds.left.toFloat(), - bounds.centerY() + startAmp * lineAmplitude * heightFraction, - wavePaint) + val startAmp = cos(abs(waveStart) / waveLength * TWO_PI) + canvas.drawPoint(0f, startAmp * lineAmplitude * heightFraction, wavePaint) + + canvas.restore() } override fun getOpacity(): Int { @@ -233,4 +232,4 @@ class SquigglyProgress : Drawable() { linePaint.color = ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index b39770d302be..2d09ddd7f630 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -310,6 +310,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements if (icon.getType() != Icon.TYPE_BITMAP && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) { // icon doesn't support getBitmap, use default value for color scheme updateButtonBackgroundColorFilter(); + updateDialogBackgroundColor(); } else { Configuration config = mContext.getResources().getConfiguration(); int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; @@ -319,11 +320,14 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements if (colorSetUpdated) { mAdapter.updateColorScheme(wallpaperColors, isDarkThemeOn); updateButtonBackgroundColorFilter(); + updateDialogBackgroundColor(); } } mHeaderIcon.setVisibility(View.VISIBLE); mHeaderIcon.setImageIcon(icon); } else { + updateButtonBackgroundColorFilter(); + updateDialogBackgroundColor(); mHeaderIcon.setVisibility(View.GONE); } if (appSourceIcon != null) { @@ -381,11 +385,16 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private void updateButtonBackgroundColorFilter() { ColorFilter buttonColorFilter = new PorterDuffColorFilter( - mAdapter.getController().getColorButtonBackground(), + mMediaOutputController.getColorButtonBackground(), PorterDuff.Mode.SRC_IN); mDoneButton.getBackground().setColorFilter(buttonColorFilter); mStopButton.getBackground().setColorFilter(buttonColorFilter); - mDoneButton.setTextColor(mAdapter.getController().getColorPositiveButtonText()); + mDoneButton.setTextColor(mMediaOutputController.getColorPositiveButtonText()); + } + + private void updateDialogBackgroundColor() { + getDialogView().getBackground().setTint(mMediaOutputController.getColorDialogBackground()); + mDeviceListLayout.setBackgroundColor(mMediaOutputController.getColorDialogBackground()); } private Drawable resizeDrawable(Drawable drawable, int size) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 96817c9bf32d..f040e067bc6d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -133,6 +133,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @VisibleForTesting LocalMediaManager mLocalMediaManager; private MediaOutputMetricLogger mMetricLogger; + private int mCurrentState; private int mColorItemContent; private int mColorSeekbarProgress; @@ -140,6 +141,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private int mColorItemBackground; private int mColorConnectedItemBackground; private int mColorPositiveButtonText; + private int mColorDialogBackground; private float mInactiveRadius; private float mActiveRadius; @@ -188,6 +190,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, R.dimen.media_output_dialog_background_radius); mActiveRadius = mContext.getResources().getDimension( R.dimen.media_output_dialog_active_background_radius); + mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext, + R.color.media_dialog_background); } void start(@NonNull Callback cb) { @@ -204,6 +208,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, if (TextUtils.equals(controller.getPackageName(), mPackageName)) { mMediaController = controller; mMediaController.unregisterCallback(mCb); + if (mMediaController.getPlaybackState() != null) { + mCurrentState = mMediaController.getPlaybackState().getState(); + } mMediaController.registerCallback(mCb); break; } @@ -461,6 +468,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mColorItemBackground = mCurrentColorScheme.getNeutral2().get(9); // N2-800 mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().get(9); // A2-800 mColorPositiveButtonText = mCurrentColorScheme.getAccent2().get(9); // A2-800 + mColorDialogBackground = mCurrentColorScheme.getNeutral1().get(10); // N1-900 } else { mColorItemContent = mCurrentColorScheme.getAccent1().get(9); // A1-800 mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(4); // A1-300 @@ -468,6 +476,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mColorItemBackground = mCurrentColorScheme.getAccent2().get(1); // A2-50 mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().get(2); // A1-100 mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().get(1); // N1-50 + mColorDialogBackground = mCurrentColorScheme.getBackgroundColor(); } } @@ -487,6 +496,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return mColorPositiveButtonText; } + public int getColorDialogBackground() { + return mColorDialogBackground; + } + public int getColorItemContent() { return mColorItemContent; } @@ -976,10 +989,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onPlaybackStateChanged(PlaybackState playbackState) { - final int state = playbackState.getState(); - if (state == PlaybackState.STATE_STOPPED || state == PlaybackState.STATE_PAUSED) { + final int newState = + playbackState == null ? PlaybackState.STATE_STOPPED : playbackState.getState(); + if (mCurrentState == newState) { + return; + } + + if (newState == PlaybackState.STATE_STOPPED || newState == PlaybackState.STATE_PAUSED) { mCallback.onMediaStoppedOrPaused(); } + mCurrentState = newState; } }; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java index 6fe06e085556..7d3e82c9d47f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java @@ -107,7 +107,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME, - getInteractionDeviceType(source)); + getInteractionDeviceType(source), + getLoggingPackageName()); } /** @@ -121,7 +122,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING, - SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE); + SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE, + getLoggingPackageName()); } /** @@ -135,7 +137,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION, - getInteractionDeviceType(source)); + getInteractionDeviceType(source), + getLoggingPackageName()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt index aa10f7e2738f..b565f3c22f24 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -18,38 +18,57 @@ package com.android.systemui.media.taptotransfer.common import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel +import com.android.systemui.temporarydisplay.TemporaryViewLogger /** * A logger for media tap-to-transfer events. * - * @property deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". + * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". */ class MediaTttLogger( - private val deviceTypeTag: String, - private val buffer: LogBuffer -){ + deviceTypeTag: String, + buffer: LogBuffer +) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) { /** Logs a change in the chip state for the given [mediaRouteId]. */ - fun logStateChange(stateName: String, mediaRouteId: String) { + fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) { buffer.log( - BASE_TAG + deviceTypeTag, + tag, LogLevel.DEBUG, { str1 = stateName str2 = mediaRouteId + str3 = packageName }, - { "State changed to $str1 for ID=$str2" } + { "State changed to $str1 for ID=$str2 package=$str3" } ) } - /** Logs that we removed the chip for the given [reason]. */ - fun logChipRemoval(reason: String) { + /** Logs that we couldn't find information for [packageName]. */ + fun logPackageNotFound(packageName: String) { buffer.log( - BASE_TAG + deviceTypeTag, + tag, LogLevel.DEBUG, - { str1 = reason }, - { "Chip removed due to $str1" } + { str1 = packageName }, + { "Package $str1 could not be found" } ) } + + /** + * Logs that a removal request has been bypassed (ignored). + * + * @param removalReason the reason that the chip removal was requested. + * @param bypassReason the reason that the request was bypassed. + */ + fun logRemovalBypass(removalReason: String, bypassReason: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = removalReason + str2 = bypassReason + }, + { "Chip removal requested due to $str1; however, removal was ignored because $str2" }) + } } private const val BASE_TAG = "MediaTtt" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt new file mode 100644 index 000000000000..792ae7ca6049 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer.common + +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import com.android.internal.widget.CachingIconView +import com.android.settingslib.Utils +import com.android.systemui.R + +/** Utility methods for media tap-to-transfer. */ +class MediaTttUtils { + companion object { + // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and + // UpdateMediaTapToTransferReceiverDisplayTest + const val WINDOW_TITLE = "Media Transfer Chip View" + const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED" + + /** + * Returns the information needed to display the icon. + * + * The information will either contain app name and icon of the app playing media, or a + * default name and icon if we can't find the app name/icon. + * + * @param appPackageName the package name of the app playing the media. + * @param logger the logger to use for any errors. + */ + fun getIconInfoFromPackageName( + context: Context, + appPackageName: String?, + logger: MediaTttLogger + ): IconInfo { + if (appPackageName != null) { + try { + val contentDescription = + context.packageManager + .getApplicationInfo( + appPackageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + .loadLabel(context.packageManager) + .toString() + return IconInfo( + contentDescription, + drawable = context.packageManager.getApplicationIcon(appPackageName), + isAppIcon = true + ) + } catch (e: PackageManager.NameNotFoundException) { + logger.logPackageNotFound(appPackageName) + } + } + return IconInfo( + contentDescription = + context.getString(R.string.media_output_dialog_unknown_launch_app_name), + drawable = + context.resources.getDrawable(R.drawable.ic_cast).apply { + this.setTint( + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + ) + }, + isAppIcon = false + ) + } + + /** + * Sets an icon to be displayed by the given view. + * + * @param iconSize the size in pixels that the icon should be. If null, the size of + * [appIconView] will not be adjusted. + */ + fun setIcon( + appIconView: CachingIconView, + icon: Drawable, + iconContentDescription: CharSequence, + iconSize: Int? = null, + ) { + iconSize?.let { size -> + val lp = appIconView.layoutParams + lp.width = size + lp.height = size + appIconView.layoutParams = lp + } + + appIconView.contentDescription = iconContentDescription + appIconView.setImageDrawable(icon) + } + } +} + +data class IconInfo( + val contentDescription: String, + val drawable: Drawable, + /** + * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. + */ + val isAppIcon: Boolean +) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 5d6d683f93f6..dfd9e22c14b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -35,6 +35,7 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS @@ -61,7 +62,7 @@ class MediaTttChipControllerReceiver @Inject constructor( powerManager: PowerManager, @Main private val mainHandler: Handler, private val uiEventLogger: MediaTttReceiverUiEventLogger, -) : TemporaryViewDisplayController<ChipReceiverInfo>( +) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>( context, logger, windowManager, @@ -70,6 +71,8 @@ class MediaTttChipControllerReceiver @Inject constructor( configurationController, powerManager, R.layout.media_ttt_chip_receiver, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, ) { @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS override val windowLayoutParams = commonWindowLayoutParams.apply { @@ -107,7 +110,7 @@ class MediaTttChipControllerReceiver @Inject constructor( ) { val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState") @@ -137,13 +140,26 @@ class MediaTttChipControllerReceiver @Inject constructor( override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { super.updateView(newInfo, currentView) - val iconName = setIcon( - currentView, - newInfo.routeInfo.clientPackageName, - newInfo.appIconDrawableOverride, - newInfo.appNameOverride + + val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + context, newInfo.routeInfo.clientPackageName, logger + ) + val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable + val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription + val iconSize = context.resources.getDimensionPixelSize( + if (iconInfo.isAppIcon) { + R.dimen.media_ttt_icon_size_receiver + } else { + R.dimen.media_ttt_generic_icon_size_receiver + } + ) + + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconDrawable, + iconContentDescription, + iconSize, ) - currentView.contentDescription = iconName } override fun animateViewIn(view: ViewGroup) { @@ -161,15 +177,6 @@ class MediaTttChipControllerReceiver @Inject constructor( startRipple(view.requireViewById(R.id.ripple)) } - override fun getIconSize(isAppIcon: Boolean): Int? = - context.resources.getDimensionPixelSize( - if (isAppIcon) { - R.dimen.media_ttt_icon_size_receiver - } else { - R.dimen.media_ttt_generic_icon_size_receiver - } - ) - /** Returns the amount that the chip will be translated by in its intro animation. */ private fun getTranslationAmount(): Int { return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index bde588c14fc8..4379d25406bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -34,8 +34,7 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS * @property stateInt the integer from [StatusBarManager] corresponding with this state. * @property stringResId the res ID of the string that should be displayed in the chip. Null if the * state should not have the chip be displayed. - * @property isMidTransfer true if the state represents that a transfer is currently ongoing. - * @property isTransferFailure true if the state represents that the transfer has failed. + * @property transferStatus the transfer status that the chip state represents. * @property timeout the amount of time this chip should display on the screen before it times out * and disappears. */ @@ -43,8 +42,7 @@ enum class ChipStateSender( @StatusBarManager.MediaTransferSenderState val stateInt: Int, val uiEvent: UiEventLogger.UiEventEnum, @StringRes val stringResId: Int?, - val isMidTransfer: Boolean = false, - val isTransferFailure: Boolean = false, + val transferStatus: TransferStatus, val timeout: Long = DEFAULT_TIMEOUT_MILLIS ) { /** @@ -56,6 +54,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST, R.string.media_move_closer_to_start_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -68,6 +67,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST, R.string.media_move_closer_to_end_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -78,7 +78,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED, R.string.media_transfer_playing_different_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -90,7 +90,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED, R.string.media_transfer_playing_this_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -100,7 +100,8 @@ enum class ChipStateSender( TRANSFER_TO_RECEIVER_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED, - R.string.media_transfer_playing_different_device + R.string.media_transfer_playing_different_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -135,7 +136,8 @@ enum class ChipStateSender( TRANSFER_TO_THIS_DEVICE_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, - R.string.media_transfer_playing_this_device + R.string.media_transfer_playing_this_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -169,7 +171,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that a transfer back to this device has failed. */ @@ -177,14 +179,15 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that this device is far away from any receiver device. */ FAR_FROM_RECEIVER( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER, - stringResId = null + stringResId = null, + transferStatus = TransferStatus.TOO_FAR, ); /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 0c1ebd70c572..e539f3fd842d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -34,6 +34,7 @@ import com.android.systemui.animation.ViewHierarchyAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason @@ -57,7 +58,7 @@ class MediaTttChipControllerSender @Inject constructor( configurationController: ConfigurationController, powerManager: PowerManager, private val uiEventLogger: MediaTttSenderUiEventLogger -) : TemporaryViewDisplayController<ChipSenderInfo>( +) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>( context, logger, windowManager, @@ -66,6 +67,8 @@ class MediaTttChipControllerSender @Inject constructor( configurationController, powerManager, R.layout.media_ttt_chip, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, ) { override val windowLayoutParams = commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) @@ -94,7 +97,7 @@ class MediaTttChipControllerSender @Inject constructor( ) { val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") @@ -103,7 +106,7 @@ class MediaTttChipControllerSender @Inject constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER::class.simpleName!!) + removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name) } else { displayView(ChipSenderInfo(chipState, routeInfo, undoCallback)) } @@ -118,7 +121,14 @@ class MediaTttChipControllerSender @Inject constructor( val chipState = newInfo.state // App icon - val iconName = setIcon(currentView, newInfo.routeInfo.clientPackageName) + val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + context, newInfo.routeInfo.clientPackageName, logger + ) + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconInfo.drawable, + iconInfo.contentDescription + ) // Text val otherDeviceName = newInfo.routeInfo.name.toString() @@ -127,7 +137,7 @@ class MediaTttChipControllerSender @Inject constructor( // Loading currentView.requireViewById<View>(R.id.loading).visibility = - chipState.isMidTransfer.visibleIfTrue() + (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue() // Undo val undoView = currentView.requireViewById<View>(R.id.undo) @@ -139,12 +149,12 @@ class MediaTttChipControllerSender @Inject constructor( // Failure currentView.requireViewById<View>(R.id.failure_icon).visibility = - chipState.isTransferFailure.visibleIfTrue() + (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue() // For accessibility currentView.requireViewById<ViewGroup>( R.id.media_ttt_sender_chip_inner - ).contentDescription = "$iconName $chipText" + ).contentDescription = "${iconInfo.contentDescription} $chipText" } override fun animateViewIn(view: ViewGroup) { @@ -162,10 +172,17 @@ class MediaTttChipControllerSender @Inject constructor( } override fun removeView(removalReason: String) { - // Don't remove the chip if we're mid-transfer since the user should still be able to - // see the status of the transfer. (But do remove it if it's finally timed out.) - if (info?.state?.isMidTransfer == true && - removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT) { + // Don't remove the chip if we're in progress or succeeded, since the user should still be + // able to see the status of the transfer. (But do remove it if it's finally timed out.) + val transferStatus = info?.state?.transferStatus + if ( + (transferStatus == TransferStatus.IN_PROGRESS || + transferStatus == TransferStatus.SUCCEEDED) && + removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT + ) { + logger.logRemovalBypass( + removalReason, bypassReason = "transferStatus=${transferStatus.name}" + ) return } super.removeView(removalReason) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt new file mode 100644 index 000000000000..f15720df5245 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer.sender + +/** Represents the different possible transfer states that we could be in. */ +enum class TransferStatus { + /** The transfer hasn't started yet. */ + NOT_STARTED, + /** The transfer is currently ongoing but hasn't completed yet. */ + IN_PROGRESS, + /** The transfer has completed successfully. */ + SUCCEEDED, + /** The transfer has completed with a failure. */ + FAILED, + /** The device is too far away to do a transfer. */ + TOO_FAR, +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 9e1ba5f723a3..97024881ca62 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -39,6 +39,7 @@ import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; +import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -51,6 +52,7 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsController.Behavior; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; @@ -78,7 +80,6 @@ import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdates import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarTransitionsController; @@ -775,13 +776,24 @@ public class NavigationBarView extends FrameLayout { updateSlippery(); reloadNavIcons(); updateNavButtonIcons(); - mBgExecutor.execute(() -> WindowManagerWrapper.getInstance() - .setNavBarVirtualKeyHapticFeedbackEnabled(!mShowSwipeUpUi)); + mBgExecutor.execute(() -> setNavBarVirtualKeyHapticFeedbackEnabled(!mShowSwipeUpUi)); getHomeButton().setAccessibilityDelegate( mShowSwipeUpUi ? mQuickStepAccessibilityDelegate : null); } /** + * Enable or disable haptic feedback on the navigation bar buttons. + */ + private void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) { + try { + WindowManagerGlobal.getWindowManagerService() + .setNavBarVirtualKeyHapticFeedbackEnabled(enabled); + } catch (RemoteException e) { + Log.w(TAG, "Failed to enable or disable navigation bar button haptics: ", e); + } + } + + /** * Updates the {@link WindowManager.LayoutParams.FLAG_SLIPPERY} state dependent on if swipe up * is enabled, or the notifications is fully opened without being in an animated state. If * slippery is enabled, touch events will leave the nav bar window and enter into the fullscreen diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 920f4634abe2..e1289a61d45d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -99,7 +99,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener // slider, as well as animating the alpha of the QS tile layout (as we are tracking QQS tiles) @Nullable private TouchAnimator mFirstPageAnimator; - // TranslationX animator for QQS/QS tiles + // TranslationX animator for QQS/QS tiles. Only used on the first page! private TouchAnimator mTranslationXAnimator; // TranslationY animator for QS tiles (and their components) in the first page private TouchAnimator mTranslationYAnimator; @@ -107,13 +107,14 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private TouchAnimator mQQSTranslationYAnimator; // Animates alpha of permanent views (QS tile layout, QQS tiles) when not in first page private TouchAnimator mNonfirstPageAlphaAnimator; - // TranslatesY the QS Tile layout using QS.getHeightDiff() - private TouchAnimator mQSTileLayoutTranslatorAnimator; // This animates fading of media player private TouchAnimator mAllPagesDelayedAnimator; - // Animator for brightness slider(s) + // Brightness slider translation driver, uses mQSExpansionPathInterpolator.yInterpolator @Nullable - private TouchAnimator mBrightnessAnimator; + private TouchAnimator mBrightnessTranslationAnimator; + // Brightness slider opacity driver. Uses linear interpolator. + @Nullable + private TouchAnimator mBrightnessOpacityAnimator; // Animator for Footer actions in QQS private TouchAnimator mQQSFooterActionsAnimator; // Height animator for QQS tiles (height changing from QQS size to QS size) @@ -137,7 +138,6 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private final QSTileHost mHost; private final Executor mExecutor; private boolean mShowCollapsedOnKeyguard; - private boolean mTranslateWhileExpanding; private int mQQSTop; private int[] mTmpLoc1 = new int[2]; @@ -298,13 +298,6 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener QSTileLayout tileLayout = mQsPanelController.getTileLayout(); mAllViews.add((View) tileLayout); - int heightDiff = mQs.getHeightDiff(); - if (!mTranslateWhileExpanding) { - heightDiff *= SHORT_PARALLAX_AMOUNT; - } - mQSTileLayoutTranslatorAnimator = new Builder() - .addFloat(tileLayout, "translationY", heightDiff, 0) - .build(); mLastQQSTileHeight = 0; @@ -407,12 +400,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener mAnimatedQsViews.add(tileView); mAllViews.add(quickTileView); mAllViews.add(quickTileView.getSecondaryLabel()); - } else if (isIconInAnimatedRow(count)) { - - firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0); - - mAllViews.add(tileIcon); - } else { + } else if (!isIconInAnimatedRow(count)) { // Pretend there's a corresponding QQS tile (for the position) that we are // expanding from. SideLabelTileLayout qqsLayout = @@ -442,7 +430,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener } } - animateBrightnessSlider(firstPageBuilder); + animateBrightnessSlider(); mFirstPageAnimator = firstPageBuilder // Fade in the tiles/labels as we reach the final position. @@ -568,7 +556,9 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener return new Pair<>(animator, builder.build()); } - private void animateBrightnessSlider(Builder firstPageBuilder) { + private void animateBrightnessSlider() { + mBrightnessTranslationAnimator = null; + mBrightnessOpacityAnimator = null; View qsBrightness = mQsPanelController.getBrightnessView(); View qqsBrightness = mQuickQSPanelController.getBrightnessView(); if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) { @@ -576,25 +566,45 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener mAnimatedQsViews.add(qsBrightness); mAllViews.add(qqsBrightness); int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness); - mBrightnessAnimator = new Builder() + mBrightnessTranslationAnimator = new Builder() // we need to animate qs brightness even if animation will not be visible, // as we might start from sliderScaleY set to 0.3 if device was in collapsed QS // portrait orientation before .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1) .addFloat(qqsBrightness, "translationY", 0, translationY) + .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()) .build(); } else if (qsBrightness != null) { - firstPageBuilder.addFloat(qsBrightness, "translationY", - qsBrightness.getMeasuredHeight() * 0.5f, 0); - mBrightnessAnimator = new Builder() + // The brightness slider's visible bottom edge must maintain a constant margin from the + // QS tiles during transition. Thus the slider must (1) perform the same vertical + // translation as the tiles, and (2) compensate for the slider scaling. + + // For (1), compute the distance via the vertical distance between QQS and QS tile + // layout top. + View quickSettingsRootView = mQs.getView(); + View qsTileLayout = (View) mQsPanelController.getTileLayout(); + View qqsTileLayout = (View) mQuickQSPanelController.getTileLayout(); + getRelativePosition(mTmpLoc1, qsTileLayout, quickSettingsRootView); + getRelativePosition(mTmpLoc2, qqsTileLayout, quickSettingsRootView); + int tileMovement = mTmpLoc2[1] - mTmpLoc1[1]; + + // For (2), the slider scales to the vertical center, so compensate with half the + // height at full collapse. + float scaleCompensation = qsBrightness.getMeasuredHeight() * 0.5f; + mBrightnessTranslationAnimator = new Builder() + .addFloat(qsBrightness, "translationY", scaleCompensation + tileMovement, 0) + .addFloat(qsBrightness, "sliderScaleY", 0, 1) + .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()) + .build(); + + // While the slider's position and unfurl is animated throughouth the motion, the + // fade in happens independently. + mBrightnessOpacityAnimator = new Builder() .addFloat(qsBrightness, "alpha", 0, 1) - .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1) - .setInterpolator(Interpolators.ALPHA_IN) - .setStartDelay(0.3f) + .setStartDelay(0.2f) + .setEndDelay(1 - 0.5f) .build(); mAllViews.add(qsBrightness); - } else { - mBrightnessAnimator = null; } } @@ -676,11 +686,13 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener if (mQQSTileHeightAnimator != null) { mQQSTileHeightAnimator.setPosition(position); } - mQSTileLayoutTranslatorAnimator.setPosition(position); mQQSTranslationYAnimator.setPosition(position); mAllPagesDelayedAnimator.setPosition(position); - if (mBrightnessAnimator != null) { - mBrightnessAnimator.setPosition(position); + if (mBrightnessOpacityAnimator != null) { + mBrightnessOpacityAnimator.setPosition(position); + } + if (mBrightnessTranslationAnimator != null) { + mBrightnessTranslationAnimator.setPosition(position); } if (mQQSFooterActionsAnimator != null) { mQQSFooterActionsAnimator.setPosition(position); @@ -774,13 +786,6 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener setCurrentPosition(); }; - /** - * True whe QS will be pulled from the top, false when it will be clipped. - */ - public void setTranslateWhileExpanding(boolean shouldTranslate) { - mTranslateWhileExpanding = shouldTranslate; - } - private static class HeightExpansionAnimator { private final List<View> mViews = new ArrayList<>(); private final ValueAnimator mAnimator; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java index b02efba93161..de11d567d858 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java @@ -39,8 +39,15 @@ public class QSDetailClipper { mBackground = (TransitionDrawable) detail.getBackground(); } - public void animateCircularClip(int x, int y, boolean in, AnimatorListener listener) { - updateCircularClip(true /* animate */, x, y, in, listener); + /** + * @param x x position where animation should originate + * @param y y position where animation should originate + * @param in whether animating in or out + * @param listener Animation listener. Called whether or not {@code animate} is true. + * @return the duration of the circular animator + */ + public long animateCircularClip(int x, int y, boolean in, AnimatorListener listener) { + return updateCircularClip(true /* animate */, x, y, in, listener); } /** @@ -50,8 +57,9 @@ public class QSDetailClipper { * @param y y position where animation should originate * @param in whether animating in or out * @param listener Animation listener. Called whether or not {@code animate} is true. + * @return the duration of the circular animator */ - public void updateCircularClip(boolean animate, int x, int y, boolean in, + public long updateCircularClip(boolean animate, int x, int y, boolean in, AnimatorListener listener) { if (mAnimator != null) { mAnimator.cancel(); @@ -87,6 +95,7 @@ public class QSDetailClipper { mAnimator.addListener(mGoneOnEnd); } mAnimator.start(); + return mAnimator.getDuration(); } private final Runnable mReverseBackground = new Runnable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 072c32f693d9..3820500c6de3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -573,7 +573,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Override public void setInSplitShade(boolean inSplitShade) { mInSplitShade = inSplitShade; - mQSAnimator.setTranslateWhileExpanding(inSplitShade); updateShowCollapsedOnKeyguard(); updateQsState(); } @@ -669,7 +668,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSPanelController.setRevealExpansion(expansion); mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); - mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff); + + float qsScrollViewTranslation = + onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0; + mQSPanelScrollView.setTranslationY(qsScrollViewTranslation); + if (fullyCollapsed) { mQSPanelScrollView.setScrollY(0); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 7155626a1aa1..448e1807f7af 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -49,7 +49,6 @@ import com.android.systemui.tuner.TunerService.Tunable; import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** View that represents the quick settings tile panel (when expanded/pulled down). **/ public class QSPanel extends LinearLayout implements Tunable { @@ -291,7 +290,16 @@ public class QSPanel extends LinearLayout implements Tunable { } else { topOffset = tileHeightOffset; } - int top = Objects.requireNonNull(mChildrenLayoutTop.get(child)); + // Animation can occur before the layout pass, meaning setSquishinessFraction() gets + // called before onLayout(). So, a child view could be null because it has not + // been added to mChildrenLayoutTop yet (which happens in onLayout()). + // We use a continue statement here to catch this NPE because, on the layout pass, + // this code will be called again from onLayout() with the populated children views. + Integer childLayoutTop = mChildrenLayoutTop.get(child); + if (childLayoutTop == null) { + continue; + } + int top = childLayoutTop; child.setLeftTopRightBottom(child.getLeft(), top + topOffset, child.getRight(), top + topOffset + child.getHeight()); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 8ad011904d3d..cf10c7940871 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -125,9 +125,10 @@ public class QSCustomizer extends LinearLayout { isShown = true; mOpening = true; setVisibility(View.VISIBLE); - mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter)); + long duration = mClipper.animateCircularClip( + mX, mY, true, new ExpandAnimatorListener(tileAdapter)); mQsContainerController.setCustomizerAnimating(true); - mQsContainerController.setCustomizerShowing(true); + mQsContainerController.setCustomizerShowing(true, duration); } } @@ -153,13 +154,14 @@ public class QSCustomizer extends LinearLayout { // Make sure we're not opening (because we're closing). Nobody can think we are // customizing after the next two lines. mOpening = false; + long duration = 0; if (animate) { - mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); + duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); } else { setVisibility(View.GONE); } mQsContainerController.setCustomizerAnimating(animate); - mQsContainerController.setCustomizerShowing(false); + mQsContainerController.setCustomizerShowing(false, duration); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 58426656e4bf..e9a6c25c0e6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -50,6 +50,7 @@ public class QSIconViewImpl extends QSIconView { protected int mIconSizePx; private boolean mAnimationEnabled = true; private int mState = -1; + private boolean mDisabledByPolicy = false; private int mTint; @Nullable private QSTile.Icon mLastIcon; @@ -159,14 +160,10 @@ public class QSIconViewImpl extends QSIconView { } protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) { - if (state.disabledByPolicy) { - iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color)); - } else { - iv.clearColorFilter(); - } - if (state.state != mState) { + if (state.state != mState || state.disabledByPolicy != mDisabledByPolicy) { int color = getColor(state); mState = state.state; + mDisabledByPolicy = state.disabledByPolicy; if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); } else { @@ -241,8 +238,8 @@ public class QSIconViewImpl extends QSIconView { */ private static int getIconColorForState(Context context, QSTile.State state) { if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) { - return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA, - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)); + return Utils.getColorAttrDefaultColor( + context, com.android.internal.R.attr.textColorTertiary); } else if (state.state == Tile.STATE_INACTIVE) { return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); } else if (state.state == Tile.STATE_ACTIVE) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 163ee2af600c..972b24343d10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -100,14 +100,15 @@ open class QSTileViewImpl @JvmOverloads constructor( Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - private val colorLabelUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorLabelInactive) + private val colorLabelUnavailable = + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) private val colorSecondaryLabelActive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse) private val colorSecondaryLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary) private val colorSecondaryLabelUnavailable = - Utils.applyAlpha(UNAVAILABLE_ALPHA, colorSecondaryLabelInactive) + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) private lateinit var label: TextView protected lateinit var secondaryLabel: TextView diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index c2a82a76d3e8..a31500c6bb18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -49,7 +49,6 @@ import javax.inject.Inject; /** Quick settings tile: Invert colors **/ public class ColorInversionTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = ResourceIcon.get(drawable.ic_invert_colors); private final SettingObserver mSetting; @Inject @@ -123,7 +122,9 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { state.value = enabled; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_inversion_label); - state.icon = mIcon; + state.icon = ResourceIcon.get(state.value + ? drawable.qs_invert_colors_icon_on + : drawable.qs_invert_colors_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 9fdf5940c392..2fc99f323611 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -128,13 +128,13 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements @Override protected void handleUpdateState(BooleanState state, Object arg) { - state.value = arg instanceof Boolean ? (Boolean) arg + state.value = arg instanceof Boolean ? ((Boolean) arg).booleanValue() : mDataSaverController.isDataSaverEnabled(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.data_saver); state.contentDescription = state.label; - state.icon = ResourceIcon.get(state.value ? R.drawable.ic_data_saver - : R.drawable.ic_data_saver_off); + state.icon = ResourceIcon.get(state.value ? R.drawable.qs_data_saver_icon_on + : R.drawable.qs_data_saver_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 81813db5abfd..0e9f6599522f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -144,9 +144,10 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements protected void handleUpdateState(BooleanState state, Object arg) { state.value = mManager.isNightDisplayActivated(); state.label = mContext.getString(R.string.quick_settings_night_display_label); - state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_night_display_on); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on + : R.drawable.qs_nightlight_icon_off); state.secondaryLabel = getSecondaryLabel(state.value); state.contentDescription = TextUtils.isEmpty(state.secondaryLabel) ? state.label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index 6921ad549626..1dac33909ba7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -49,7 +49,6 @@ import javax.inject.Named; public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> implements ReduceBrightColorsController.Listener{ - private final Icon mIcon = ResourceIcon.get(drawable.ic_reduce_bright_colors); private final boolean mIsAvailable; private final ReduceBrightColorsController mReduceBrightColorsController; private boolean mIsListening; @@ -111,7 +110,9 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> state.label = mContext.getString(R.string.reduce_bright_colors_feature_name); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; - state.icon = mIcon; + state.icon = ResourceIcon.get(state.value + ? drawable.qs_extra_dim_icon_on + : drawable.qs_extra_dim_icon_off); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index e2964eaf2a23..f60e0661a235 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles; import android.app.UiModeManager; +import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.os.Handler; @@ -60,9 +61,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements ConfigurationController.ConfigurationListener, BatteryController.BatteryStateChangeCallback { public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a"); - private final Icon mIcon = ResourceIcon.get( - com.android.internal.R.drawable.ic_qs_ui_mode_night); - private UiModeManager mUiModeManager; + private final UiModeManager mUiModeManager; private final BatteryController mBatteryController; private final LocationController mLocationController; @Inject @@ -82,7 +81,8 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; - mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); + mUiModeManager = (UiModeManager) host.getUserContext().getSystemService( + Context.UI_MODE_SERVICE); mLocationController = locationController; configurationController.observe(getLifecycle(), this); batteryController.observe(getLifecycle(), this); @@ -155,7 +155,6 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements } state.value = nightMode; state.label = mContext.getString(R.string.quick_settings_ui_mode_night_label); - state.icon = mIcon; state.contentDescription = TextUtils.isEmpty(state.secondaryLabel) ? state.label : TextUtils.concat(state.label, ", ", state.secondaryLabel); @@ -164,6 +163,9 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements } else { state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } + state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE + ? R.drawable.qs_light_dark_theme_icon_on + : R.drawable.qs_light_dark_theme_icon_off); state.showRippleEffect = false; state.expandedAccessibilityClassName = Switch.class.getName(); } 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 2861ed7edf37..24c4723d8b50 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 @@ -198,6 +198,8 @@ public class InternetDialog extends SystemUIDialog implements mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW); mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, null); + mDialogView.setAccessibilityPaneTitle( + mContext.getText(R.string.accessibility_desc_quick_settings)); final Window window = getWindow(); window.setContentView(mDialogView); diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 06c80a1ca57d..2ee5f05549cf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -37,10 +37,12 @@ import android.os.RemoteException; import android.text.SpannableStringBuilder; import android.text.style.BulletSpan; import android.util.DisplayMetrics; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.animation.DecelerateInterpolator; import android.widget.Button; @@ -54,7 +56,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.util.leak.RotationUtils; @@ -67,6 +68,7 @@ import dagger.Lazy; public class ScreenPinningRequest implements View.OnClickListener, NavigationModeController.ModeChangedListener { + private static final String TAG = "ScreenPinningRequest"; private final Context mContext; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; @@ -248,9 +250,8 @@ public class ScreenPinningRequest implements View.OnClickListener, mLayout.findViewById(R.id.screen_pinning_text_area) .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); View buttons = mLayout.findViewById(R.id.screen_pinning_buttons); - WindowManagerWrapper wm = WindowManagerWrapper.getInstance(); if (!QuickStepContract.isGesturalMode(mNavBarMode) - && wm.hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) { + && hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) { buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); swapChildrenIfRtlAndVertical(buttons); } else { @@ -321,6 +322,20 @@ public class ScreenPinningRequest implements View.OnClickListener, addView(mLayout, getRequestLayoutParams(rotation)); } + /** + * @param displayId the id of display to check if there is a software navigation bar. + * + * @return whether there is a soft nav bar on specific display. + */ + private boolean hasSoftNavigationBar(int displayId) { + try { + return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(displayId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to check soft navigation bar", e); + return false; + } + } + private void swapChildrenIfRtlAndVertical(View group) { if (mContext.getResources().getConfiguration().getLayoutDirection() != View.LAYOUT_DIRECTION_RTL) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt index 246265b2c202..ac5640b14716 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt @@ -24,8 +24,9 @@ import android.os.IBinder import android.util.Log import android.view.DisplayAddress import android.view.SurfaceControl -import android.view.SurfaceControl.DisplayCaptureArgs -import android.view.SurfaceControl.ScreenshotHardwareBuffer +import android.window.ScreenCapture +import android.window.ScreenCapture.DisplayCaptureArgs +import android.window.ScreenCapture.ScreenshotHardwareBuffer import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -84,6 +85,6 @@ open class ImageCaptureImpl @Inject constructor( .setSize(width, height) .setSourceCrop(crop) .build() - return SurfaceControl.captureDisplay(captureArgs) + return ScreenCapture.captureDisplay(captureArgs) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 6b32daff0ab1..fe40d4cbe23a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -30,6 +30,7 @@ import androidx.constraintlayout.motion.widget.MotionLayout import com.android.settingslib.Utils import com.android.systemui.Dumpable import com.android.systemui.R +import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -310,6 +311,14 @@ class LargeScreenShadeHeaderController @Inject constructor( updateVisibility() } + fun startCustomizingAnimation(show: Boolean, duration: Long) { + header.animate() + .setDuration(duration) + .alpha(if (show) 0f else 1f) + .setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN) + .start() + } + private fun loadConstraints() { if (header is MotionLayout) { // Use resources.getXml instead of passing the resource id due to bug b/205018300 diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 156b123cee8d..1011a6d831e6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; +import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED; @@ -62,6 +63,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; +import android.os.Process; import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; @@ -236,6 +238,9 @@ public final class NotificationPanelViewController extends PanelViewController { private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DEBUG_DRAWABLE = false; + private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false); + /** * The parallax amount of the quick settings translation when dragging down the panel */ @@ -443,6 +448,8 @@ public final class NotificationPanelViewController extends PanelViewController { private boolean mQsTouchAboveFalsingThreshold; private int mQsFalsingThreshold; + /** Indicates drag starting height when swiping down or up on heads-up notifications */ + private int mHeadsUpStartHeight; private HeadsUpTouchHelper mHeadsUpTouchHelper; private boolean mListenForHeadsUp; private int mNavigationBarBottomHeight; @@ -645,6 +652,8 @@ public final class NotificationPanelViewController extends PanelViewController { /** The drag distance required to fully expand the split shade. */ private int mSplitShadeFullTransitionDistance; + /** The drag distance required to fully transition scrims. */ + private int mSplitShadeScrimTransitionDistance; private final NotificationListContainer mNotificationListContainer; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; @@ -678,14 +687,22 @@ public final class NotificationPanelViewController extends PanelViewController { private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() { @Override - public void onDoubleTapRequired() { + public void onAdditionalTapRequired() { if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { mTapAgainViewController.show(); } else { mKeyguardIndicationController.showTransientIndication( R.string.notification_tap_again); } - mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + if (!mStatusBarStateController.isDozing()) { + mVibratorHelper.vibrate( + Process.myUid(), + mView.getContext().getPackageName(), + ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT, + "falsing-additional-tap-required", + TOUCH_VIBRATION_ATTRIBUTES); + } } }; @@ -1052,6 +1069,8 @@ public final class NotificationPanelViewController extends PanelViewController { mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( R.dimen.notification_side_paddings); mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize( + R.dimen.split_shade_scrim_transition_distance); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, @@ -1335,7 +1354,7 @@ public final class NotificationPanelViewController extends PanelViewController { mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mQsSizeChangeAnimator.addUpdateListener(animation -> { requestScrollerTopPaddingUpdate(false /* animate */); - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); int height = (int) mQsSizeChangeAnimator.getAnimatedValue(); mQs.setHeightOverride(height); }); @@ -2114,7 +2133,7 @@ public final class NotificationPanelViewController extends PanelViewController { mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1); setQsExpandImmediate(true); setShowShelfOnly(true); - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); // Normally, we start listening when the panel is expanded, but here we need to start // earlier so the state is already up to date when dragging down. @@ -2326,7 +2345,7 @@ public final class NotificationPanelViewController extends PanelViewController { // Reset scroll position and apply that position to the expanded height. float height = mQsExpansionHeight; setQsExpansion(height); - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); mNotificationStackScrollLayoutController.checkSnoozeLeavebehind(); // When expanding QS, let's authenticate the user if possible, @@ -2342,7 +2361,7 @@ public final class NotificationPanelViewController extends PanelViewController { if (changed) { mQsExpanded = expanded; updateQsState(); - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); mFalsingCollector.setQsExpanded(expanded); mCentralSurfaces.setQsExpanded(expanded); mNotificationsQSContainerController.setQsExpanded(expanded); @@ -3066,16 +3085,7 @@ public final class NotificationPanelViewController extends PanelViewController { int maxHeight; if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted || mPulsing || mSplitShadeEnabled) { - if (mSplitShadeEnabled && mBarState == SHADE) { - // Max panel height is used to calculate the fraction of the shade expansion. - // Traditionally the value is based on the number of notifications. - // On split-shade, we want the required distance to be a specific and constant - // value, to make sure the expansion motion has the expected speed. - // We also only want this on non-lockscreen for now. - maxHeight = mSplitShadeFullTransitionDistance; - } else { - maxHeight = calculatePanelHeightQsExpanded(); - } + maxHeight = calculatePanelHeightQsExpanded(); } else { maxHeight = calculatePanelHeightShade(); } @@ -3434,6 +3444,31 @@ public final class NotificationPanelViewController extends PanelViewController { } @Override + public int getMaxPanelTransitionDistance() { + // Traditionally the value is based on the number of notifications. On split-shade, we want + // the required distance to be a specific and constant value, to make sure the expansion + // motion has the expected speed. We also only want this on non-lockscreen for now. + if (mSplitShadeEnabled && mBarState == SHADE) { + boolean transitionFromHeadsUp = + mHeadsUpManager.isTrackingHeadsUp() || mExpandingFromHeadsUp; + // heads-up starting height is too close to mSplitShadeFullTransitionDistance and + // when dragging HUN transition is already 90% complete. It makes shade become + // immediately visible when starting to drag. We want to set distance so that + // nothing is immediately visible when dragging (important for HUN swipe up motion) - + // 0.4 expansion fraction is a good starting point. + if (transitionFromHeadsUp) { + double maxDistance = Math.max(mSplitShadeFullTransitionDistance, + mHeadsUpStartHeight * 2.5); + return (int) Math.min(getMaxPanelHeight(), maxDistance); + } else { + return mSplitShadeFullTransitionDistance; + } + } else { + return getMaxPanelHeight(); + } + } + + @Override protected boolean isTrackingBlocked() { return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; } @@ -3632,10 +3667,35 @@ public final class NotificationPanelViewController extends PanelViewController { } /** + * Called when heads-up notification is being dragged up or down to indicate what's the starting + * height for shade motion + */ + public void setHeadsUpDraggingStartingHeight(int startHeight) { + mHeadsUpStartHeight = startHeight; + float scrimMinFraction; + if (mSplitShadeEnabled) { + boolean highHun = mHeadsUpStartHeight * 2.5 > mSplitShadeScrimTransitionDistance; + // if HUN height is higher than 40% of predefined transition distance, it means HUN + // is too high for regular transition. In that case we need to calculate transition + // distance - here we take scrim transition distance as equal to shade transition + // distance. It doesn't result in perfect motion - usually scrim transition distance + // should be longer - but it's good enough for HUN case. + float transitionDistance = + highHun ? getMaxPanelTransitionDistance() : mSplitShadeFullTransitionDistance; + scrimMinFraction = mHeadsUpStartHeight / transitionDistance; + } else { + int transitionDistance = getMaxPanelHeight(); + scrimMinFraction = transitionDistance > 0f + ? (float) mHeadsUpStartHeight / transitionDistance : 0f; + } + setPanelScrimMinFraction(scrimMinFraction); + } + + /** * Sets the minimum fraction for the panel expansion offset. This may be non-zero in certain * cases, such as if there's a heads-up notification. */ - public void setPanelScrimMinFraction(float minFraction) { + private void setPanelScrimMinFraction(float minFraction) { mMinFraction = minFraction; mDepthController.setPanelPullDownMinFraction(mMinFraction); mScrimController.setPanelScrimMinFraction(mMinFraction); @@ -4368,7 +4428,7 @@ public final class NotificationPanelViewController extends PanelViewController { if (mKeyguardShowing) { updateMaxDisplayedNotifications(true); } - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); } @Override @@ -4501,7 +4561,7 @@ public final class NotificationPanelViewController extends PanelViewController { if (mQsExpanded && mQsFullyExpanded) { mQsExpansionHeight = mQsMaxExpansionHeight; requestScrollerTopPaddingUpdate(false /* animate */); - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); } if (mAccessibilityManager.isEnabled()) { mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); @@ -4770,7 +4830,7 @@ public final class NotificationPanelViewController extends PanelViewController { if (mQsExpanded && mQsFullyExpanded) { mQsExpansionHeight = mQsMaxExpansionHeight; requestScrollerTopPaddingUpdate(false /* animate */); - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); // Size has changed, start an animation. if (mQsMaxExpansionHeight != oldMaxHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index abafecce2965..8d74a09ab8b1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -73,7 +73,7 @@ public class NotificationShadeWindowViewController { private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; - private GestureDetector mGestureDetector; + private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; private boolean mTouchCancelled; @@ -149,7 +149,8 @@ public class NotificationShadeWindowViewController { /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ public void setupExpandedStatusBar() { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); - mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener); + mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), + mPulsingGestureListener); mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override @@ -196,7 +197,7 @@ public class NotificationShadeWindowViewController { } mFalsingCollector.onTouchEvent(ev); - mGestureDetector.onTouchEvent(ev); + mPulsingWakeupGestureHandler.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 2a467763951c..d6f0de83ecc1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -35,6 +35,7 @@ class NotificationsQSContainerController @Inject constructor( view: NotificationsQuickSettingsContainer, private val navigationModeController: NavigationModeController, private val overviewProxyService: OverviewProxyService, + private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController, private val featureFlags: FeatureFlags, @Main private val delayableExecutor: DelayableExecutor ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { @@ -156,9 +157,12 @@ class NotificationsQSContainerController @Inject constructor( } } - override fun setCustomizerShowing(showing: Boolean) { - isQSCustomizing = showing - updateBottomSpacing() + override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) { + if (showing != isQSCustomizing) { + isQSCustomizing = showing + largeScreenShadeHeaderController.startCustomizingAnimation(showing, animationDuration) + updateBottomSpacing() + } } override fun setDetailShowing(showing: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java index a00769374938..c3f1e571ab87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java @@ -248,7 +248,7 @@ public abstract class PanelViewController { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); } }); mAmbientState = ambientState; @@ -730,7 +730,7 @@ public abstract class PanelViewController { setExpandedHeightInternal(height); } - protected void requestPanelHeightUpdate() { + void updateExpandedHeightToMaxHeight() { float currentMaxPanelHeight = getMaxPanelHeight(); if (isFullyCollapsed()) { @@ -753,6 +753,13 @@ public abstract class PanelViewController { setExpandedHeight(currentMaxPanelHeight); } + /** + * Returns drag down distance after which panel should be fully expanded. Usually it's the + * same as max panel height but for large screen devices (especially split shade) we might + * want to return different value to shorten drag distance + */ + public abstract int getMaxPanelTransitionDistance(); + public void setExpandedHeightInternal(float h) { if (isNaN(h)) { Log.wtf(TAG, "ExpandedHeight set to NaN"); @@ -763,18 +770,15 @@ public abstract class PanelViewController { () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); mExpandLatencyTracking = false; } - float maxPanelHeight = getMaxPanelHeight(); + float maxPanelHeight = getMaxPanelTransitionDistance(); if (mHeightAnimator == null) { // Split shade has its own overscroll logic if (mTracking && !mInSplitShade) { float overExpansionPixels = Math.max(0, h - maxPanelHeight); setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); } - mExpandedHeight = Math.min(h, maxPanelHeight); - } else { - mExpandedHeight = h; } - + mExpandedHeight = Math.min(h, maxPanelHeight); // If we are closing the panel and we are almost there due to a slow decelerating // interpolator, abort the animation. if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { @@ -832,7 +836,7 @@ public abstract class PanelViewController { protected abstract int getMaxPanelHeight(); public void setExpandedFraction(float frac) { - setExpandedHeight(getMaxPanelHeight() * frac); + setExpandedHeight(getMaxPanelTransitionDistance() * frac); } public float getExpandedHeight() { @@ -1029,7 +1033,7 @@ public abstract class PanelViewController { mHeightAnimator = animator; if (animator == null && mPanelUpdateWhenAnimatorEnds) { mPanelUpdateWhenAnimatorEnds = false; - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); } } @@ -1421,7 +1425,7 @@ public abstract class PanelViewController { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - requestPanelHeightUpdate(); + updateExpandedHeightToMaxHeight(); mHasLayoutedSinceDown = true; if (mUpdateFlingOnLayout) { abortAnimations(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index 9b3fe92f9225..084b7dc3a646 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -26,6 +26,7 @@ import com.android.systemui.Dumpable import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent @@ -36,12 +37,12 @@ import javax.inject.Inject /** * If tap and/or double tap to wake is enabled, this gestureListener will wake the display on - * tap/double tap when the device is pulsing (AoD 2). Taps are gated by the proximity sensor and - * falsing manager. + * tap/double tap when the device is pulsing (AoD2) or transitioning to AoD. Taps are gated by the + * proximity sensor and falsing manager. * - * Touches go through the [NotificationShadeWindowViewController] when the device is pulsing. - * Otherwise, if the device is dozing and NOT pulsing, wake-ups are handled by - * [com.android.systemui.doze.DozeSensors]. + * Touches go through the [NotificationShadeWindowViewController] when the device is dozing but the + * screen is still ON and not in the true AoD display state. When the device is in the true AoD + * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors]. */ @CentralSurfacesComponent.CentralSurfacesScope class PulsingGestureListener @Inject constructor( @@ -75,12 +76,12 @@ class PulsingGestureListener @Inject constructor( dumpManager.registerDumpable(this) } - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (statusBarStateController.isPulsing && + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (statusBarStateController.isDozing && singleTapEnabled && !dockManager.isDocked && !falsingManager.isProximityNear && - !falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY) + !falsingManager.isFalseTap(LOW_PENALTY) ) { centralSurfaces.wakeUpIfDozing( SystemClock.uptimeMillis(), @@ -91,8 +92,15 @@ class PulsingGestureListener @Inject constructor( return false } - override fun onDoubleTap(e: MotionEvent): Boolean { - if (statusBarStateController.isPulsing && + /** + * Receives [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], and [MotionEvent.ACTION_UP] + * motion events for a double tap. + */ + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + // React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing + // checks MUST be on the ACTION_UP event. + if (e.actionMasked == MotionEvent.ACTION_UP && + statusBarStateController.isDozing && (doubleTapEnabled || singleTapEnabled) && !falsingManager.isProximityNear && !falsingManager.isFalseDoubleTap diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt index afd57daca10b..618c8924cd21 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt @@ -14,6 +14,7 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.phone.panelstate.PanelState import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.LargeScreenUtils import java.io.PrintWriter import javax.inject.Inject @@ -28,6 +29,7 @@ constructor( private val scrimController: ScrimController, @Main private val resources: Resources, private val statusBarStateController: SysuiStatusBarStateController, + private val headsUpManager: HeadsUpManager ) { private var inSplitShade = false @@ -84,7 +86,11 @@ constructor( } private fun canUseCustomFraction(panelState: Int?) = - inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING + inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING && + // in case of HUN we can't always use predefined distances to manage scrim + // transition because dragDownPxAmount can start from value bigger than + // splitShadeScrimTransitionDistance + !headsUpManager.isTrackingHeadsUp private fun isScreenUnlocked() = statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 408c61f8f387..e06c97756ea7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,9 +19,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_FIRST_FRAME_RECEIVED; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_START; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.view.View.GONE; @@ -53,6 +50,7 @@ import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; @@ -82,7 +80,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; -import com.android.systemui.biometrics.BiometricMessageDeferral; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -184,6 +182,7 @@ public class KeyguardIndicationController { private long mChargingTimeRemaining; private String mBiometricErrorMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow; + private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -233,7 +232,8 @@ public class KeyguardIndicationController { LockPatternUtils lockPatternUtils, ScreenLifecycle screenLifecycle, KeyguardBypassController keyguardBypassController, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + FaceHelpMessageDeferral faceHelpMessageDeferral) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -254,6 +254,7 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); + mFaceAcquiredMessageDeferral = faceHelpMessageDeferral; mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); @@ -1041,8 +1042,22 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.processFrame(acquireInfo); + } + } + + @Override public void onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString); + if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { + return; + } + } + // TODO(b/141025588): refactor to reduce repetition of code/comments // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to @@ -1053,17 +1068,6 @@ public class KeyguardIndicationController { return; } - if (biometricSourceType == FACE) { - if (msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED) { - mFaceAcquiredMessageDeferral.reset(); - } else { - mFaceAcquiredMessageDeferral.processMessage(msgId, helpString); - if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { - return; - } - } - } - final boolean faceAuthSoftError = biometricSourceType == FACE && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; final boolean faceAuthFailed = biometricSourceType == FACE @@ -1109,11 +1113,23 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.reset(); + } + } + + @Override public void onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType) { CharSequence deferredFaceMessage = null; if (biometricSourceType == FACE) { - deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { + deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (DEBUG) { + Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage); + } + } mFaceAcquiredMessageDeferral.reset(); } @@ -1308,14 +1324,4 @@ public class KeyguardIndicationController { } } }; - - private final BiometricMessageDeferral mFaceAcquiredMessageDeferral = - new BiometricMessageDeferral( - Set.of( - FACE_ACQUIRED_GOOD, - FACE_ACQUIRED_START, - FACE_ACQUIRED_FIRST_FRAME_RECEIVED - ), - Set.of(FACE_ACQUIRED_TOO_DARK) - ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 867be1ab305f..c070fccf9808 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -40,7 +40,7 @@ import javax.inject.Inject; public class VibratorHelper { private final Vibrator mVibrator; - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 31b21c9b5321..553826dda919 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -136,7 +136,7 @@ class NotificationLaunchAnimatorController( headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate) } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started // here? notificationShadeWindowViewController.setExpandAnimationRunning(false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index ba26cfaa30b4..df81c0ed3a61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1369,6 +1369,8 @@ public class NotificationContentView extends FrameLayout implements Notification } ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button); View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); + LinearLayout actionListMarginTarget = layout.findViewById( + com.android.internal.R.id.notification_action_list_margin_target); if (bubbleButton == null || actionContainer == null) { return; } @@ -1393,6 +1395,16 @@ public class NotificationContentView extends FrameLayout implements Notification bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener()); bubbleButton.setVisibility(VISIBLE); actionContainer.setVisibility(VISIBLE); + // Set notification_action_list_margin_target's bottom margin to 0 when showing bubble + if (actionListMarginTarget != null) { + ViewGroup.LayoutParams lp = actionListMarginTarget.getLayoutParams(); + if (lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + if (mlp.bottomMargin > 0) { + mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, 0); + } + } + } } else { bubbleButton.setVisibility(GONE); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 0b2d443598d2..4576a6442838 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -23,7 +23,6 @@ import android.content.res.Resources; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricSourceType; -import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; @@ -654,10 +653,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp final boolean fingerprintLockout = biometricSourceType == BiometricSourceType.FINGERPRINT && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT); - final boolean faceLockout = biometricSourceType == BiometricSourceType.FACE - && (msgId == FaceManager.FACE_ERROR_LOCKOUT - || msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT); - if (fingerprintLockout || faceLockout) { + if (fingerprintLockout) { startWakeAndUnlock(MODE_SHOW_BOUNCER); UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 7463dac6b5d2..1eafaf0615a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -99,11 +99,14 @@ import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; @@ -509,10 +512,17 @@ public class CentralSurfacesImpl extends CoreStartable implements private CentralSurfacesComponent mCentralSurfacesComponent; // Flags for disabling the status bar - // Two variables becaseu the first one evidently ran out of room for new flags. + // Two variables because the first one evidently ran out of room for new flags. private int mDisabled1 = 0; private int mDisabled2 = 0; + /** + * This keeps track of whether we have (or haven't) registered the predictive back callback. + * Since we can have visible -> visible transitions, we need to avoid + * double-registering (or double-unregistering) our callback. + */ + private boolean mIsBackCallbackRegistered = false; + /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ private @Appearance int mAppearance; @@ -636,6 +646,12 @@ public class CentralSurfacesImpl extends CoreStartable implements private final InteractionJankMonitor mJankMonitor; + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + if (DEBUG) { + Log.d(TAG, "mOnBackInvokedCallback() called"); + } + onBackPressed(); + }; /** * Public constructor for CentralSurfaces. @@ -1703,13 +1719,18 @@ public class CentralSurfacesImpl extends CoreStartable implements } @Override - public void onLaunchAnimationCancelled() { + public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { + if (newKeyguardOccludedState != null) { + mKeyguardViewMediator.setOccluded( + newKeyguardOccludedState, false /* animate */); + } + // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the // animation so that we can assume that mIsLaunchingActivityOverLockscreen // being true means that we will collapse the shade (or at least run the // post collapse runnables) later on. CentralSurfacesImpl.this.mIsLaunchingActivityOverLockscreen = false; - getDelegate().onLaunchAnimationCancelled(); + getDelegate().onLaunchAnimationCancelled(newKeyguardOccludedState); } }; } else if (dismissShade) { @@ -2731,9 +2752,38 @@ public class CentralSurfacesImpl extends CoreStartable implements if (visibleToUser) { handleVisibleToUserChangedImpl(visibleToUser); mNotificationLogger.startNotificationLogging(); + + if (!mIsBackCallbackRegistered) { + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, + mOnBackInvokedCallback); + mIsBackCallbackRegistered = true; + if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered"); + } + } else { + if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered"); + } } else { mNotificationLogger.stopNotificationLogging(); handleVisibleToUserChangedImpl(visibleToUser); + + if (mIsBackCallbackRegistered) { + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.getOnBackInvokedDispatcher() + .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + mIsBackCallbackRegistered = false; + if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered"); + } + } else { + if (DEBUG) { + Log.d(TAG, + "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY " + + "unregistered)"); + } + } } } @@ -3444,6 +3494,12 @@ public class CentralSurfacesImpl extends CoreStartable implements return mNotificationPanelViewController.getKeyguardBottomAreaView(); } + protected ViewRootImpl getViewRootImpl() { + NotificationShadeWindowView nswv = getNotificationShadeWindowView(); + if (nswv != null) return nswv.getViewRootImpl(); + + return null; + } /** * Propagation of the bouncer state, indicating that it's fully visible. */ @@ -3552,6 +3608,7 @@ public class CentralSurfacesImpl extends CoreStartable implements dismissVolumeDialog(); mWakeUpCoordinator.setFullyAwake(false); mKeyguardBypassController.onStartedGoingToSleep(); + mStatusBarTouchableRegionManager.updateTouchableRegion(); // The unlocked screen off and fold to aod animations might use our LightRevealScrim - // we need to be expanded for it to be visible. @@ -3580,6 +3637,7 @@ public class CentralSurfacesImpl extends CoreStartable implements // once we fully woke up. updateRevealEffect(true /* wakingUp */); updateNotificationPanelTouchState(); + mStatusBarTouchableRegionManager.updateTouchableRegion(); // If we are waking up during the screen off animation, we should undo making the // expanded visible (we did that so the LightRevealScrim would be visible). @@ -3761,6 +3819,12 @@ public class CentralSurfacesImpl extends CoreStartable implements updateScrimController(); } + @VisibleForTesting + public void setNotificationShadeWindowViewController( + NotificationShadeWindowViewController nswvc) { + mNotificationShadeWindowViewController = nswvc; + } + /** * Set the amount of progress we are currently in if we're transitioning to the full shade. * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 6bfb0dad28f7..90d0b697337a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -115,9 +115,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mInitialTouchY = y; int startHeight = (int) (mPickedChild.getActualHeight() + mPickedChild.getTranslationY()); - float maxPanelHeight = mPanel.getMaxPanelHeight(); - mPanel.setPanelScrimMinFraction(maxPanelHeight > 0f - ? (float) startHeight / maxPanelHeight : 0f); + mPanel.setHeadsUpDraggingStartingHeight(startHeight); mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight); // This call needs to be after the expansion start otherwise we will get a // flicker of one frame as it's not expanded yet. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index e63c383b010b..44aef7de4013 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -220,6 +220,7 @@ public class KeyguardBouncer { && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser()) && !needsFullscreenBouncer() + && !mKeyguardUpdateMonitor.isFaceLockedOut() && !mKeyguardUpdateMonitor.userNeedsStrongAuth() && !mKeyguardBypassController.getBypassEnabled()) { mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index c0922163903f..ee948c04df38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -51,7 +51,7 @@ class StatusBarLaunchAnimatorController( centralSurfaces.notificationPanelViewController.applyLaunchAnimationProgress(linearProgress) } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { delegate.onLaunchAnimationCancelled() centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(false) centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 75dac1a093dd..d9c0293fe13d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -55,6 +55,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final Context mContext; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationShadeWindowController mNotificationShadeWindowController; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; private boolean mShouldAdjustInsets = false; @@ -72,7 +73,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { Context context, NotificationShadeWindowController notificationShadeWindowController, ConfigurationController configurationController, - HeadsUpManagerPhone headsUpManager + HeadsUpManagerPhone headsUpManager, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController ) { mContext = context; initResources(); @@ -115,6 +117,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> { updateTouchableRegion(); }); + + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } protected void setup( @@ -179,7 +183,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { /** * Set the touchable portion of the status bar based on what elements are visible. */ - private void updateTouchableRegion() { + public void updateTouchableRegion() { boolean hasCutoutInset = (mNotificationShadeWindowView != null) && (mNotificationShadeWindowView.getRootWindowInsets() != null) && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null); @@ -242,12 +246,25 @@ public final class StatusBarTouchableRegionManager implements Dumpable { touchableRegion.union(bounds); } + /** + * Helper to let us know when calculating the region is not needed because we know the entire + * screen needs to be touchable. + */ + private boolean shouldMakeEntireScreenTouchable() { + // The touchable region is always the full area when expanded, whether we're showing the + // shade or the bouncer. It's also fully touchable when the screen off animation is playing + // since we don't want stray touches to go through the light reveal scrim to whatever is + // underneath. + return mIsStatusBarExpanded + || mCentralSurfaces.isBouncerShowing() + || mUnlockedScreenOffAnimationController.isAnimationPlaying(); + } + private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = new OnComputeInternalInsetsListener() { @Override public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsStatusBarExpanded || mCentralSurfaces.isBouncerShowing()) { - // The touchable region is always the full area when expanded + if (shouldMakeEntireScreenTouchable()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt index a6160aaf7756..6b7c42e3884a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt @@ -114,8 +114,8 @@ class PanelExpansionStateManager @Inject constructor() { "end state=${state.panelStateToString()} " + "f=$fraction " + "expanded=$expanded " + - "tracking=$tracking" + - "drawDownPxAmount=$dragDownPxAmount " + + "tracking=$tracking " + + "dragDownPxAmount=$dragDownPxAmount " + "${if (fullyOpened) " fullyOpened" else ""} " + if (fullyClosed) " fullyClosed" else "" ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt deleted file mode 100644 index fe846747d0c6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ /dev/null @@ -1,55 +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.statusbar.pipeline - -import android.content.Context -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel -import javax.inject.Inject -import javax.inject.Provider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -/** - * A temporary object that collects on [WifiViewModel] flows for debugging purposes. - * - * This will eventually get migrated to a view binder that will use the flow outputs to set state on - * views. For now, this just collects on flows so that the information gets logged. - */ -@SysUISingleton -class ConnectivityInfoProcessor @Inject constructor( - context: Context, - // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's - // scope so we only do work when there's UI that cares about it. - @Application private val scope: CoroutineScope, - private val statusBarPipelineFlags: StatusBarPipelineFlags, - private val wifiViewModelProvider: Provider<WifiViewModel>, -) : CoreStartable(context) { - override fun start() { - if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) { - return - } - // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can - // see the logs. - scope.launch { - wifiViewModelProvider.get().isActivityInVisible.collect { } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 681dc6fc13a2..9a7c3fae780c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -16,25 +16,15 @@ package com.android.systemui.statusbar.pipeline.dagger -import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl import dagger.Binds import dagger.Module -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap @Module abstract class StatusBarPipelineModule { - /** Inject into ConnectivityInfoProcessor. */ - @Binds - @IntoMap - @ClassKey(ConnectivityInfoProcessor::class) - abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable - @Binds abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index e7fa6d239012..aae0f93a0e19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,7 +27,6 @@ import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.os.UserManager; -import android.util.Log; import androidx.annotation.NonNull; @@ -37,6 +36,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -57,9 +57,9 @@ import javax.inject.Inject; public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener { private static final String TAG = "BluetoothController"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final DumpManager mDumpManager; + private final BluetoothLogger mLogger; private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; @@ -70,6 +70,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>(); private boolean mEnabled; + @ConnectionState private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; private boolean mAudioProfileOnly; private boolean mIsActive; @@ -83,10 +84,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa public BluetoothControllerImpl( Context context, DumpManager dumpManager, + BluetoothLogger logger, @Background Looper bgLooper, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) { mDumpManager = dumpManager; + mLogger = logger; mLocalBluetoothManager = localBluetoothManager; mBgHandler = new Handler(bgLooper); mHandler = new H(mainLooper); @@ -116,7 +119,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return; } pw.print(" mEnabled="); pw.println(mEnabled); - pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState)); + pw.print(" mConnectionState="); pw.println(connectionStateToString(mConnectionState)); pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly); pw.print(" mIsActive="); pw.println(mIsActive); pw.print(" mConnectedDevices="); pw.println(getConnectedDevices()); @@ -127,7 +130,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } } - private static String stateToString(int state) { + private static String connectionStateToString(@ConnectionState int state) { switch (state) { case BluetoothAdapter.STATE_CONNECTED: return "CONNECTED"; @@ -320,8 +323,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override - public void onBluetoothStateChanged(int bluetoothState) { - if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState)); + public void onBluetoothStateChanged(@AdapterState int bluetoothState) { + mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState)); mEnabled = bluetoothState == BluetoothAdapter.STATE_ON || bluetoothState == BluetoothAdapter.STATE_TURNING_ON; mState = bluetoothState; @@ -330,24 +333,25 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override - public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { - if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress()); + public void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) { + mLogger.logDeviceAdded(cachedDevice.getAddress()); cachedDevice.registerCallback(this); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { - if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress()); + public void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) { + mLogger.logDeviceDeleted(cachedDevice.getAddress()); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { - if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress()); + public void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) { + mLogger.logBondStateChange(cachedDevice.getAddress(), bondState); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); @@ -355,50 +359,47 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public void onDeviceAttributesChanged() { - if (DEBUG) Log.d(TAG, "DeviceAttributesChanged"); + mLogger.logDeviceAttributesChanged(); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - if (DEBUG) { - Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state)); - } + public void onConnectionStateChanged( + @Nullable CachedBluetoothDevice cachedDevice, + @ConnectionState int state) { + mLogger.logDeviceConnectionStateChanged( + getAddressOrNull(cachedDevice), connectionStateToString(state)); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { - if (DEBUG) { - Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state) + " profileId=" + bluetoothProfile); - } + public void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { + mLogger.logProfileConnectionStateChanged( + cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (DEBUG) { - Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress() - + " profileId=" + bluetoothProfile); - } + public void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { + mLogger.logActiveDeviceChanged(getAddressOrNull(activeDevice), bluetoothProfile); updateActive(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - if (DEBUG) { - Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state)); - } + public void onAclConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int state) { + mLogger.logAclConnectionStateChanged( + cachedDevice.getAddress(), connectionStateToString(state)); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); @@ -415,6 +416,11 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return state; } + @Nullable + private String getAddressOrNull(@Nullable CachedBluetoothDevice device) { + return device == null ? null : device.getAddress(); + } + @Override public void onServiceConnected() { updateConnected(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 3b8ed338db80..63e88a6cde67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -1122,11 +1122,19 @@ public class UserSwitcherController implements Dumpable { } public String getName(Context context, UserRecord item) { + return getName(context, item, false); + } + + /** + * Returns the name for the given {@link UserRecord}. + */ + public String getName(Context context, UserRecord item, boolean isTablet) { return LegacyUserUiHelper.getUserRecordName( context, item, mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting()); + mController.isGuestUserResetting(), + isTablet); } protected static ColorFilter getDisabledUserAvatarColorFilter() { @@ -1136,8 +1144,12 @@ public class UserSwitcherController implements Dumpable { } protected static Drawable getIconDrawable(Context context, UserRecord item) { + return getIconDrawable(context, item, false); + } + protected static Drawable getIconDrawable(Context context, UserRecord item, + boolean isTablet) { int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser); + item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); return context.getDrawable(iconRes); } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 734eeecec215..a52e2aff52c1 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -19,12 +19,10 @@ package com.android.systemui.temporarydisplay import android.annotation.LayoutRes import android.annotation.SuppressLint import android.content.Context -import android.content.pm.PackageManager import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.PowerManager import android.os.SystemClock -import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.view.WindowManager @@ -33,11 +31,7 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT import androidx.annotation.CallSuper -import com.android.internal.widget.CachingIconView -import com.android.settingslib.Utils -import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor @@ -50,17 +44,22 @@ import com.android.systemui.util.concurrency.DelayableExecutor * The generic type T is expected to contain all the information necessary for the subclasses to * display the view in a certain state, since they receive <T> in [updateView]. * - * TODO(b/245610654): Remove all the media-specific logic from this class. + * @property windowTitle the title to use for the window that displays the temporary view. Should be + * normally cased, like "Window Title". + * @property wakeReason a string used for logging if we needed to wake the screen in order to + * display the temporary view. Should be screaming snake cased, like WAKE_REASON. */ -abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( +abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>( internal val context: Context, - internal val logger: MediaTttLogger, + internal val logger: U, internal val windowManager: WindowManager, @Main private val mainExecutor: DelayableExecutor, private val accessibilityManager: AccessibilityManager, private val configurationController: ConfigurationController, private val powerManager: PowerManager, @LayoutRes private val viewLayoutRes: Int, + private val windowTitle: String, + private val wakeReason: String, ) { /** * Window layout params that will be used as a starting point for the [windowLayoutParams] of @@ -72,7 +71,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( height = WindowManager.LayoutParams.WRAP_CONTENT type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - title = WINDOW_TITLE + title = windowTitle format = PixelFormat.TRANSLUCENT setTrustedOverlay() } @@ -115,10 +114,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( powerManager.wakeUp( SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION, - "com.android.systemui:media_tap_to_transfer_activated" + "com.android.systemui:$wakeReason", ) } - + logger.logChipAddition() inflateAndUpdateView(newInfo) } @@ -192,80 +191,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( * appears. */ open fun animateViewIn(view: ViewGroup) {} - - /** - * Returns the size that the icon should be, or null if no size override is needed. - */ - open fun getIconSize(isAppIcon: Boolean): Int? = null - - /** - * An internal method to set the icon on the view. - * - * This is in the common superclass since both the sender and the receiver show an icon. - * - * @param appPackageName the package name of the app playing the media. Will be used to fetch - * the app icon and app name if overrides aren't provided. - * - * @return the content description of the icon. - */ - internal fun setIcon( - currentView: ViewGroup, - appPackageName: String?, - appIconDrawableOverride: Drawable? = null, - appNameOverride: CharSequence? = null, - ): CharSequence { - val appIconView = currentView.requireViewById<CachingIconView>(R.id.app_icon) - val iconInfo = getIconInfo(appPackageName) - - getIconSize(iconInfo.isAppIcon)?.let { size -> - val lp = appIconView.layoutParams - lp.width = size - lp.height = size - appIconView.layoutParams = lp - } - - appIconView.contentDescription = appNameOverride ?: iconInfo.iconName - appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon) - return appIconView.contentDescription - } - - /** - * Returns the information needed to display the icon. - * - * The information will either contain app name and icon of the app playing media, or a default - * name and icon if we can't find the app name/icon. - */ - private fun getIconInfo(appPackageName: String?): IconInfo { - if (appPackageName != null) { - try { - return IconInfo( - iconName = context.packageManager.getApplicationInfo( - appPackageName, PackageManager.ApplicationInfoFlags.of(0) - ).loadLabel(context.packageManager).toString(), - icon = context.packageManager.getApplicationIcon(appPackageName), - isAppIcon = true - ) - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "Cannot find package $appPackageName", e) - } - } - return IconInfo( - iconName = context.getString(R.string.media_output_dialog_unknown_launch_app_name), - icon = context.resources.getDrawable(R.drawable.ic_cast).apply { - this.setTint( - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - ) - }, - isAppIcon = false - ) - } } -// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and -// UpdateMediaTapToTransferReceiverDisplayTest -private const val WINDOW_TITLE = "Media Transfer Chip View" -private val TAG = TemporaryViewDisplayController::class.simpleName!! - object TemporaryDisplayRemovalReason { const val REASON_TIMEOUT = "TIMEOUT" const val REASON_SCREEN_TAP = "SCREEN_TAP" diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt new file mode 100644 index 000000000000..606a11a84686 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -0,0 +1,36 @@ +/* + * 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.temporarydisplay + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel + +/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */ +open class TemporaryViewLogger( + internal val buffer: LogBuffer, + internal val tag: String, +) { + /** Logs that we added the chip to a new window. */ + fun logChipAddition() { + buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" }) + } + + /** Logs that we removed the chip for the given [reason]. */ + fun logChipRemoval(reason: String) { + buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 8a51cd6c7e94..5e2dde6be046 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -36,6 +36,8 @@ import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.TextView +import android.window.OnBackInvokedCallback +import android.window.OnBackInvokedDispatcher import androidx.activity.ComponentActivity import androidx.constraintlayout.helper.widget.Flow import androidx.lifecycle.ViewModelProvider @@ -67,7 +69,7 @@ private const val USER_VIEW = "user_view" /** * Support a fullscreen user switcher */ -class UserSwitcherActivity @Inject constructor( +open class UserSwitcherActivity @Inject constructor( private val userSwitcherController: UserSwitcherController, private val broadcastDispatcher: BroadcastDispatcher, private val falsingCollector: FalsingCollector, @@ -83,6 +85,7 @@ class UserSwitcherActivity @Inject constructor( private var popupMenu: UserSwitcherPopupMenu? = null private lateinit var addButton: View private var addUserRecords = mutableListOf<UserRecord>() + private val onBackCallback = OnBackInvokedCallback { finish() } private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) { finish() @@ -105,7 +108,11 @@ class UserSwitcherActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + createActivity() + } + @VisibleForTesting + fun createActivity() { setContentView(R.layout.user_switcher_fullscreen) window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION @@ -148,6 +155,9 @@ class UserSwitcherActivity @Inject constructor( } } + onBackInvokedDispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback) + userSwitcherController.init(parent) initBroadcastReceiver() @@ -163,8 +173,8 @@ class UserSwitcherActivity @Inject constructor( this, R.layout.user_switcher_fullscreen_popup_item, layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) }, - { item: UserRecord -> adapter.findUserIcon(item).mutate().apply { + { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, + { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply { setTint(resources.getColor( R.color.user_switcher_fullscreen_popup_item_tint, getTheme() @@ -278,7 +288,12 @@ class UserSwitcherActivity @Inject constructor( if (isUsingModernArchitecture()) { return } + destroyActivity() + } + @VisibleForTesting + fun destroyActivity() { + onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback) broadcastDispatcher.unregisterReceiver(broadcastReceiver) userTracker.removeCallback(userSwitchedCallback) } @@ -307,6 +322,9 @@ class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } + /** + * Provides views to populate the option menu. + */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -360,20 +378,20 @@ class UserSwitcherActivity @Inject constructor( return view } - override fun getName(context: Context, item: UserRecord): String { + override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { return if (item == manageUserRecord) { getString(R.string.manage_users) } else { - super.getName(context, item) + super.getName(context, item, isTablet) } } - fun findUserIcon(item: UserRecord): Drawable { + fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable { if (item == manageUserRecord) { return getDrawable(R.drawable.ic_manage_users) } if (item.info == null) { - return getIconDrawable(this@UserSwitcherActivity, item) + return getIconDrawable(this@UserSwitcherActivity, item, isTablet) } val userIcon = userManager.getUserIcon(item.info.id) if (userIcon != null) { diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt index 18369d9a71d2..15fdc352d864 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt @@ -49,8 +49,11 @@ object LegacyUserUiHelper { isAddUser: Boolean, isGuest: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { - return if (isAddUser) { + return if (isAddUser && isTablet) { + R.drawable.ic_account_circle_filled + } else if (isAddUser) { R.drawable.ic_add } else if (isGuest) { R.drawable.ic_account_circle @@ -67,6 +70,7 @@ object LegacyUserUiHelper { record: UserRecord, isGuestUserAutoCreated: Boolean, isGuestUserResetting: Boolean, + isTablet: Boolean = false, ): String { val resourceId: Int? = getGuestUserRecordNameResourceId(record) return when { @@ -80,6 +84,7 @@ object LegacyUserUiHelper { isGuestUserResetting = isGuestUserResetting, isAddUser = record.isAddUser, isAddSupervisedUser = record.isAddSupervisedUser, + isTablet = isTablet, ) ) } @@ -108,12 +113,14 @@ object LegacyUserUiHelper { isGuestUserResetting: Boolean, isAddUser: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { check(isGuest || isAddUser || isAddSupervisedUser) return when { isGuest && isGuestUserAutoCreated && isGuestUserResetting -> com.android.settingslib.R.string.guest_resetting + isGuest && isTablet -> com.android.settingslib.R.string.guest_new_guest isGuest && isGuestUserAutoCreated -> com.android.internal.R.string.guest_name isGuest -> com.android.internal.R.string.guest_name isAddUser -> com.android.settingslib.R.string.user_add_user diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt index 83a3d0d0457a..d7ad3cefaf06 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt @@ -129,7 +129,10 @@ object UserSwitcherViewBinder { viewModel.users.collect { users -> val viewPool = view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList() - viewPool.forEach { view.removeView(it) } + viewPool.forEach { + view.removeView(it) + flowWidget.removeView(it) + } users.forEach { userViewModel -> val userView = if (viewPool.isNotEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 66ce01b7a86e..398341d256d2 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -20,6 +20,7 @@ package com.android.systemui.user.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.android.systemui.R +import com.android.systemui.common.ui.drawable.CircularDrawable import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper @@ -130,7 +131,7 @@ private constructor( return UserViewModel( viewKey = model.id, name = model.name, - image = model.image, + image = CircularDrawable(model.image), isSelectionMarkerVisible = model.isSelected, alpha = if (model.isSelectable) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index c7ba5182eef8..903aba1a74a4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -48,6 +48,7 @@ import android.app.KeyguardManager; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -74,6 +75,7 @@ import android.os.VibrationEffect; import android.provider.Settings; import android.provider.Settings.Global; import android.text.InputFilter; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; @@ -1049,7 +1051,13 @@ public class VolumeDialogImpl implements VolumeDialog, Events.writeEvent(Events.EVENT_SETTINGS_CLICK); dismissH(DISMISS_REASON_SETTINGS_CLICKED); mMediaOutputDialogFactory.dismiss(); - mVolumePanelFactory.create(true /* aboveStatusBar */, null); + if (FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI)) { + mVolumePanelFactory.create(true /* aboveStatusBar */, null); + } else { + mActivityStarter.startActivity(new Intent(Settings.Panel.ACTION_VOLUME), + true /* dismissShade */); + } }); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index 0bf038d45af0..2714cf427e19 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -90,6 +90,8 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( onlyFaceEnrolled = false, faceAuthenticated = false, faceDisabled = false, + faceLockedOut = false, + fpLockedOut = false, goingToSleep = false, keyguardAwakeExcludingBouncerShowing = false, keyguardGoingAway = false, @@ -99,5 +101,5 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( scanningAllowedByStrongAuth = false, secureCameraLaunched = false, switchingUser = false, - udfpsBouncerShowing = false + udfpsBouncerShowing = false, ) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt new file mode 100644 index 000000000000..9e5bfe53ea05 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -0,0 +1,103 @@ +/* + * 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.keyguard + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import androidx.test.filters.SmallTest +import com.android.internal.util.LatencyTracker +import com.android.internal.widget.LockPatternUtils +import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.statusbar.policy.DevicePostureController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardPinViewControllerTest : SysuiTestCase() { + @Mock private lateinit var keyguardPinView: KeyguardPINView + + @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea + + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Mock private lateinit var securityMode: SecurityMode + + @Mock private lateinit var lockPatternUtils: LockPatternUtils + + @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback + + @Mock + private lateinit var keyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory + + @Mock + private lateinit var keyguardMessageAreaController: + KeyguardMessageAreaController<BouncerKeyguardMessageArea> + + @Mock private lateinit var mLatencyTracker: LatencyTracker + + @Mock private lateinit var liftToActivateListener: LiftToActivateListener + + @Mock private val mEmergencyButtonController: EmergencyButtonController? = null + private val falsingCollector: FalsingCollector = FalsingCollectorFake() + @Mock lateinit var postureController: DevicePostureController + + lateinit var pinViewController: KeyguardPinViewController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area)) + .thenReturn(keyguardMessageArea) + Mockito.`when`( + keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java)) + ) + .thenReturn(keyguardMessageAreaController) + pinViewController = + KeyguardPinViewController( + keyguardPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + mKeyguardSecurityCallback, + keyguardMessageAreaControllerFactory, + mLatencyTracker, + liftToActivateListener, + mEmergencyButtonController, + falsingCollector, + postureController + ) + } + + @Test + fun startAppearAnimation() { + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin) + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 093e59255f65..508f81d4ee8c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -20,6 +20,7 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -751,8 +752,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); - mKeyguardUpdateMonitor.mFaceAuthenticationCallback - .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); + faceAuthLockedOut(); verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt()); } @@ -764,7 +764,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback - .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, ""); + .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, ""); verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt()); } @@ -775,10 +775,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); - mKeyguardUpdateMonitor.mFaceAuthenticationCallback - .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); + faceAuthLockedOut(); mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback - .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, ""); + .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, ""); verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt()); } @@ -1217,7 +1216,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException { // Face auth should run when the following is true. keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); + occludingAppRequestsFaceAuth(); currentUserIsPrimary(); strongAuthNotRequired(); biometricsEnabledForCurrentUser(); @@ -1225,6 +1224,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { biometricsNotDisabledThroughDevicePolicyManager(); userNotCurrentlySwitching(); mTestableLooper.processAllMessages(); + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); // Fingerprint is locked out. fingerprintErrorLockedOut(); @@ -1500,6 +1500,27 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse() + throws RemoteException { + // Preconditions for face auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true); + mTestableLooper.processAllMessages(); + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); + + // Face is locked out. + faceAuthLockedOut(); + mTestableLooper.processAllMessages(); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); + } + + @Test public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth() throws RemoteException { // Both fingerprint and face are enrolled by default @@ -1587,6 +1608,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); } + private void faceAuthLockedOut() { + mKeyguardUpdateMonitor.mFaceAuthenticationCallback + .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); + } + private void faceAuthEnabled() { // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the // face manager mock wire-up in setup() diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index e2790e47fe06..a61cd23b60fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -161,7 +161,18 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() - verify(controller).onLaunchAnimationCancelled() + verify(controller).onLaunchAnimationCancelled(false /* newKeyguardOccludedState */) + verify(controller, never()).onLaunchAnimationStart(anyBoolean()) + } + + @Test + fun passesOccludedStateToLaunchAnimationCancelled_ifTrue() { + val runner = activityLaunchAnimator.createRunner(controller) + runner.onAnimationCancelled(true /* isKeyguardOccluded */) + runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) + + waitForIdleSync() + verify(controller).onLaunchAnimationCancelled(true /* newKeyguardOccludedState */) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) } @@ -253,7 +264,7 @@ private class TestLaunchAnimatorController(override var launchContainer: ViewGro assertOnMainThread() } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { assertOnMainThread() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index bf3788e4c76a..4a5b23c02e40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -29,6 +29,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils +import android.view.KeyEvent import android.view.View import android.view.WindowInsets import android.view.WindowManager @@ -92,6 +93,21 @@ class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testDismissesOnBack() { + val container = initializeFingerprintContainer(addToView = true) + assertThat(container.parent).isNotNull() + val root = container.rootView + + // Simulate back invocation + container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)) + container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)) + waitForIdleSync() + + assertThat(container.parent).isNull() + assertThat(root.isAttachedToWindow).isFalse() + } + + @Test fun testIgnoresAnimatedInWhenDismissed() { val container = initializeFingerprintContainer(addToView = false) container.dismissFromSystemServer() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt deleted file mode 100644 index 419fedf99c15..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt +++ /dev/null @@ -1,147 +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.biometrics - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class BiometricMessageDeferralTest : SysuiTestCase() { - - @Test - fun testProcessNoMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf()) - - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testProcessNonDeferredMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there are no deferred messages processed - for (i in 0..3) { - biometricMessageDeferral.processMessage(4, "test") - } - - // THEN getDeferredMessage is null - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testAllProcessedMessagesWereDeferred() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN all the processed messages are a deferred message - for (i in 0..3) { - biometricMessageDeferral.processMessage(1, "test") - } - - // THEN deferredMessage will return the string associated with the deferred msgId - assertEquals("test", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testReturnsMostFrequentDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there's two msgId=1 processed and one msgId=2 processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN the most frequent deferred message is that meets the threshold is returned - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_mustMeetThreshold() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN more nonDeferredMessages are shown than the deferred message - val totalMessages = 10 - val nonDeferredMessagesCount = - (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until nonDeferredMessagesCount) { - biometricMessageDeferral.processMessage(4, "non-deferred-msg") - } - for (i in nonDeferredMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there's no deferred message because it didn't meet the threshold - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_manyExcludedMessages_getDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1)) - - // WHEN more excludedMessages are shown than the deferred message - val totalMessages = 10 - val excludedMessagesCount = (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until excludedMessagesCount) { - biometricMessageDeferral.processMessage(3, "excluded-msg") - } - for (i in excludedMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there IS a deferred message because the deferred msg meets the threshold amongst the - // non-excluded messages - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testResetClearsOutCounts() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // GIVEN two msgId=1 events processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - - // WHEN counts are reset and then a single deferred message is processed (msgId=2) - biometricMessageDeferral.reset() - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN msgId-2 is the deferred message since the two msgId=1 events were reset - assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testShouldDefer() { - // GIVEN should defer msgIds 1 and 2 - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1, 2)) - - // THEN shouldDefer returns true for ids 1 & 2 - assertTrue(biometricMessageDeferral.shouldDefer(1)) - assertTrue(biometricMessageDeferral.shouldDefer(2)) - - // THEN should defer returns false for ids 3 & 4 - assertFalse(biometricMessageDeferral.shouldDefer(3)) - assertFalse(biometricMessageDeferral.shouldDefer(4)) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt new file mode 100644 index 000000000000..c9ccdb36da89 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FaceHelpMessageDeferralTest : SysuiTestCase() { + val threshold = .75f + @Mock lateinit var logger: BiometricMessageDeferralLogger + @Mock lateinit var dumpManager: DumpManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testProcessFrame_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.processFrame(1) + verify(logger).logFrameProcessed(1, 1, "1") + } + + @Test + fun testUpdateMessage_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.updateMessage(1, "hi") + verify(logger).logUpdateMessage(1, "hi") + } + + @Test + fun testReset_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.reset() + verify(logger).reset() + } + + @Test + fun testProcessNoMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(emptySet()) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessNonDeferredMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there are no deferred messages processed + for (i in 0..3) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "test") + } + + // THEN getDeferredMessage is null + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessMessagesWithDeferredMessage_deferredMessageWasNeverGivenAString() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + biometricMessageDeferral.processFrame(1) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testAllProcessedMessagesWereDeferred() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN all the processed messages are a deferred message + for (i in 0..3) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "test") + } + + // THEN deferredMessage will return the string associated with the deferred msgId + assertEquals("test", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testReturnsMostFrequentDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2 + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN the most frequent deferred message is that meets the threshold is returned + assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testDeferredMessage_mustMeetThreshold() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN more nonDeferredMessages are shown than the deferred message + val totalMessages = 10 + val nonDeferredMessagesCount = (totalMessages * threshold).toInt() + 1 + for (i in 0 until nonDeferredMessagesCount) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "non-deferred-msg") + } + for (i in nonDeferredMessagesCount until totalMessages) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + } + + // THEN there's no deferred message because it didn't meet the threshold + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testResetClearsOutCounts() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // GIVEN two msgId=1 events processed + biometricMessageDeferral.processFrame( + 1, + ) + biometricMessageDeferral.updateMessage(1, "msgId-1") + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + // WHEN counts are reset and then a single deferred message is processed (msgId=2) + biometricMessageDeferral.reset() + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN msgId-2 is the deferred message since the two msgId=1 events were reset + assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testShouldDefer() { + // GIVEN should defer msgIds 1 and 2 + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // THEN shouldDefer returns true for ids 1 & 2 + assertTrue(biometricMessageDeferral.shouldDefer(1)) + assertTrue(biometricMessageDeferral.shouldDefer(2)) + + // THEN should defer returns false for ids 3 & 4 + assertFalse(biometricMessageDeferral.shouldDefer(3)) + assertFalse(biometricMessageDeferral.shouldDefer(4)) + } + + private fun createMsgDeferral(messagesToDefer: Set<Int>): BiometricMessageDeferral { + return BiometricMessageDeferral(messagesToDefer, threshold, logger, dumpManager) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index acb5622c9790..3e9cf1e51b63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -229,7 +229,10 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testAvoidDozingNotPulsing() { + public void testGestureWhenDozing() { + // We check the FalsingManager for taps during the transition to AoD (dozing=true, + // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur + // while the device is dozing. MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); @@ -239,13 +242,13 @@ public class FalsingCollectorImplTest extends SysuiTestCase { mFalsingCollector.onTouchEvent(down); verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); - // Up event would normally flush the up event, but doesn't. + // Up event flushes mFalsingCollector.onTouchEvent(up); - verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class)); } @Test - public void testAvoidDozingButPulsing() { + public void testGestureWhenPulsing() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 7f6b79b48939..4ebae98d1246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -310,7 +310,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { @Test public void testOnViewDetachedRemovesViews() { mController.onViewDetached(); - verify(mView).removeAllStatusBarItemViews(); + verify(mView).removeAllExtraStatusBarItemViews(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index eecbee56d203..a78c902a1f30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -27,11 +27,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor.forClass import org.mockito.Mock +import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -60,7 +60,18 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub - private lateinit var remoteAnimationTarget: RemoteAnimationTarget + private var surfaceControl1 = mock(SurfaceControl::class.java) + private var remoteTarget1 = RemoteAnimationTarget( + 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + + private var surfaceControl2 = mock(SurfaceControl::class.java) + private var remoteTarget2 = RemoteAnimationTarget( + 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget> @Before fun setUp() { @@ -77,10 +88,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { // All of these fields are final, so we can't mock them, but are needed so that the surface // appear amount setter doesn't short circuit. - remoteAnimationTarget = RemoteAnimationTarget( - 0, 0, null, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), - mock(WindowConfiguration::class.java), false, mock(SurfaceControl::class.java), Rect(), - mock(ActivityManager.RunningTaskInfo::class.java), false) + remoteAnimationTargets = arrayOf(remoteTarget1) // Set the surface applier to our mock so that we can verify the arguments passed to it. // This applier does not have any side effects within the unlock animation controller, so @@ -99,7 +107,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -130,7 +138,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -154,7 +162,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -176,7 +184,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -196,7 +204,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Test fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -210,7 +218,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(notificationShadeWindowController.isLaunchingActivity).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -225,11 +233,46 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) } + + /** + * If we are not wake and unlocking, we expect the unlock animation to play normally. + */ + @Test + fun surfaceAnimation_multipleTargets() { + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + arrayOf(remoteTarget1, remoteTarget2), + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + // Set appear to 50%, we'll just verify that we're not applying the identity matrix which + // means an animation is in progress. + keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f) + + val captor = forClass(SyncRtSurfaceTransactionApplier.SurfaceParams::class.java) + verify(surfaceTransactionApplier, times(2)).scheduleApply(captor.capture()) + + val allParams = captor.allValues + + val remainingTargets = mutableListOf(surfaceControl1, surfaceControl2) + allParams.forEach { params -> + assertTrue(!params.matrix.isIdentity) + remainingTargets.remove(params.surface) + } + + // Make sure we called applyParams with each of the surface controls once. The order does + // not matter, so don't explicitly check for that. + assertTrue(remainingTargets.isEmpty()) + + // Since the animation is running, we should not have finished the remote animation. + verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt index d95e5c48256c..1078cdaa57c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -23,11 +23,11 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.LogcatEchoTracker import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import java.io.PrintWriter -import java.io.StringWriter @SmallTest class MediaTttLoggerTest : SysuiTestCase() { @@ -43,32 +43,46 @@ class MediaTttLoggerTest : SysuiTestCase() { } @Test - fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() { + fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() { val stateName = "test state name" val id = "test id" + val packageName = "this.is.a.package" - logger.logStateChange(stateName, id) - - val stringWriter = StringWriter() - buffer.dump(PrintWriter(stringWriter), tailLength = 0) - val actualString = stringWriter.toString() + logger.logStateChange(stateName, id, packageName) + val actualString = getStringFromBuffer() assertThat(actualString).contains(DEVICE_TYPE_TAG) assertThat(actualString).contains(stateName) assertThat(actualString).contains(id) + assertThat(actualString).contains(packageName) } @Test - fun logChipRemoval_bufferHasDeviceTypeAndReason() { - val reason = "test reason" - logger.logChipRemoval(reason) + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" + logger.logPackageNotFound(packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(packageName) + } + + @Test + fun logRemovalBypass_bufferHasReasons() { + val removalReason = "fakeRemovalReason" + val bypassReason = "fakeBypassReason" + + logger.logRemovalBypass(removalReason, bypassReason) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(removalReason) + assertThat(actualString).contains(bypassReason) + } + + private fun getStringFromBuffer(): String { val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) - val actualString = stringWriter.toString() - - assertThat(actualString).contains(DEVICE_TYPE_TAG) - assertThat(actualString).contains(reason) + return stringWriter.toString() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt new file mode 100644 index 000000000000..37f6434ea069 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer.common + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.internal.widget.CachingIconView +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +class MediaTttUtilsTest : SysuiTestCase() { + + private lateinit var appIconFromPackageName: Drawable + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var logger: MediaTttLogger + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // Set up our package manager to give valid information for [PACKAGE_NAME] only + appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!! + whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName) + whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) + whenever( + packageManager.getApplicationInfo(any(), any<PackageManager.ApplicationInfoFlags>()) + ) + .thenThrow(PackageManager.NameNotFoundException()) + whenever( + packageManager.getApplicationInfo( + Mockito.eq(PACKAGE_NAME), + any<PackageManager.ApplicationInfoFlags>() + ) + ) + .thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + } + + @Test + fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { + val iconInfo = + MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) + + assertThat(iconInfo.isAppIcon).isFalse() + assertThat(iconInfo.contentDescription) + .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + } + + @Test + fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() { + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger) + + assertThat(iconInfo.isAppIcon).isFalse() + assertThat(iconInfo.contentDescription) + .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + } + + @Test + fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() { + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger) + + assertThat(iconInfo.isAppIcon).isTrue() + assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName) + assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun setIcon_viewHasIconAndContentDescription() { + val view = CachingIconView(context) + val icon = context.getDrawable(R.drawable.ic_celebration)!! + val contentDescription = "Happy birthday!" + + MediaTttUtils.setIcon(view, icon, contentDescription) + + assertThat(view.drawable).isEqualTo(icon) + assertThat(view.contentDescription).isEqualTo(contentDescription) + } + + @Test + fun setIcon_iconSizeNull_viewSizeDoesNotChange() { + val view = CachingIconView(context) + val size = 456 + view.layoutParams = FrameLayout.LayoutParams(size, size) + + MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc") + + assertThat(view.layoutParams.width).isEqualTo(size) + assertThat(view.layoutParams.height).isEqualTo(size) + } + + @Test + fun setIcon_iconSizeProvided_viewSizeUpdates() { + val view = CachingIconView(context) + val size = 456 + view.layoutParams = FrameLayout.LayoutParams(size, size) + + val newSize = 40 + MediaTttUtils.setIcon( + view, + context.getDrawable(R.drawable.ic_cake)!!, + "desc", + iconSize = newSize + ) + + assertThat(view.layoutParams.width).isEqualTo(newSize) + assertThat(view.layoutParams.height).isEqualTo(newSize) + } +} + +private const val PACKAGE_NAME = "com.android.systemui" +private const val APP_NAME = "Fake App Name" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index e7b4593b0ebb..d41ad48676b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -173,37 +173,72 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test - fun setIcon_isAppIcon_usesAppIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_noOverrides_usesInfoFromAppIcon() { + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun updateView_appIconOverride_usesOverride() { + val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride) + } + + @Test + fun updateView_appNameOverride_usesOverride() { + val appNameOverride = "Sweet New App" + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride) + ) + + val view = getChipView() + assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride) + } + + @Test + fun updateView_isAppIcon_usesAppIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, PACKAGE_NAME) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = true) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @Test - fun setIcon_notAppIcon_usesGenericIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_notAppIcon_usesGenericIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, appPackageName = null) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = false) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @@ -226,8 +261,13 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { return viewCaptor.value as ViewGroup } - private fun getChipReceiverInfo(): ChipReceiverInfo = - ChipReceiverInfo(routeInfo, null, null) + private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { + val routeInfo = MediaRoute2Info.Builder("id", "Test route name") + .addFeature("feature") + .setClientPackageName(packageName) + .build() + return ChipReceiverInfo(routeInfo, null, null) + } private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 52b6eed9a14d..ff0faf98fe1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -299,7 +299,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test @@ -587,15 +587,29 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeExecutor.runAllReady() verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) } @Test - fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToReceiverTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) - controllerSender.removeView("fakeRemovalReason") + fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverTriggered()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) @@ -610,20 +624,106 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeExecutor.runAllReady() verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) } @Test - fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToThisDeviceTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) + fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceTriggered()) + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) } + @Test + fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) private fun ViewGroup.getChipText(): String = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index b98be75a51c7..2db58be15665 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -158,6 +158,13 @@ class QSPanelTest : SysuiTestCase() { assertThat(qsPanel.paddingBottom).isEqualTo(padding) } + @Test + fun testSetSquishinessFraction_noCrash() { + qsPanel.addView(qsPanel.mTileLayout as View, 0) + qsPanel.addView(FrameLayout(context)) + qsPanel.setSquishinessFraction(0.5f) + } + private infix fun View.isLeftOf(other: View): Boolean { val rect = Rect() getBoundsOnScreen(rect) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index bf682a8d4e0a..3fd25019e2a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -33,12 +33,15 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; @@ -54,6 +57,8 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ColorInversionTileTest extends SysuiTestCase { + private static final Integer COLOR_INVERSION_DISABLED = 0; + private static final Integer COLOR_INVERSION_ENABLED = 1; @Mock private QSTileHost mHost; @@ -113,4 +118,24 @@ public class ColorInversionTileTest extends SysuiTestCase { assertThat(IntentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_COLOR_INVERSION_SETTINGS); } + + @Test + public void testIcon_whenColorInversionDisabled_isOffState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off)); + } + + @Test + public void testIcon_whenColorInversionEnabled_isOnState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt new file mode 100644 index 000000000000..ce62f2d1cf36 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.DataSaverController +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class DataSaverTileTest : SysuiTestCase() { + + @Mock private lateinit var mHost: QSHost + @Mock private lateinit var mMetricsLogger: MetricsLogger + @Mock private lateinit var mStatusBarStateController: StatusBarStateController + @Mock private lateinit var mActivityStarter: ActivityStarter + @Mock private lateinit var mQsLogger: QSLogger + private val falsingManager = FalsingManagerFake() + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var dataSaverController: DataSaverController + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + + private val uiEventLogger = UiEventLoggerFake() + private lateinit var testableLooper: TestableLooper + private lateinit var tile: DataSaverTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + Mockito.`when`(mHost.context).thenReturn(mContext) + Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger) + + tile = + DataSaverTile( + mHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + mMetricsLogger, + statusBarStateController, + activityStarter, + mQsLogger, + dataSaverController, + dialogLaunchAnimator + ) + } + + @Test + fun testIcon_whenDataSaverEnabled_isOnState() { + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, true) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on)) + } + + @Test + fun testIcon_whenDataSaverDisabled_isOffState() { + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, false) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt new file mode 100644 index 000000000000..188c3a3d9e42 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.LocationController +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class NightDisplayTileTest : SysuiTestCase() { + @Mock + private lateinit var mHost: QSHost + + @Mock + private lateinit var mMetricsLogger: MetricsLogger + + @Mock + private lateinit var mStatusBarStateController: StatusBarStateController + + @Mock + private lateinit var mActivityStarter: ActivityStarter + + @Mock + private lateinit var mQsLogger: QSLogger + + @Mock + private lateinit var mLocationController: LocationController + + @Mock + private lateinit var mColorDisplayManager: ColorDisplayManager + + @Mock + private lateinit var mNightDisplayListenerBuilder: NightDisplayListenerModule.Builder + + @Mock + private lateinit var mNightDisplayListener: NightDisplayListener + + private lateinit var mTestableLooper: TestableLooper + private lateinit var mTile: NightDisplayTile + + private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mTestableLooper = TestableLooper.get(this) + whenever(mHost.context).thenReturn(mContext) + whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger) + whenever(mHost.userContext).thenReturn(mContext) + whenever(mNightDisplayListenerBuilder.setUser(anyInt())).thenReturn( + mNightDisplayListenerBuilder + ) + whenever(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener) + + mTile = NightDisplayTile( + mHost, + mTestableLooper.looper, + Handler(mTestableLooper.looper), + FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQsLogger, + mLocationController, + mColorDisplayManager, + mNightDisplayListenerBuilder + ) + } + + @Test + fun testIcon_whenDisabled_showsOffState() { + whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(false) + val state = QSTile.BooleanState() + + mTile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off)) + } + + @Test + fun testIcon_whenEnabled_showsOnState() { + whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(true) + val state = QSTile.BooleanState() + + mTile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 9eb688de3511..8601d6c0a357 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -32,13 +32,16 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R.drawable; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import org.junit.Before; @@ -130,4 +133,26 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { .setReduceBrightColorsActivated(eq(true)); } + @Test + public void testIcon_whenTileEnabled_isOnState() { + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(true); + mTile.refreshState(); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_on)); + } + + @Test + public void testIcon_whenTileDisabled_isOffState() { + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false); + mTile.refreshState(); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_off)); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt new file mode 100644 index 000000000000..ea70c263a121 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.app.UiModeManager +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Handler +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.LocationController +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class UiModeNightTileTest : SysuiTestCase() { + + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var uiModeManager: UiModeManager + @Mock + private lateinit var resources: Resources + @Mock + private lateinit var qsLogger: QSLogger + @Mock + private lateinit var qsHost: QSTileHost + @Mock + private lateinit var metricsLogger: MetricsLogger + @Mock + private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var activityStarter: ActivityStarter + @Mock + private lateinit var configurationController: ConfigurationController + @Mock + private lateinit var batteryController: BatteryController + @Mock + private lateinit var locationController: LocationController + + private val uiEventLogger = UiEventLoggerFake() + private val falsingManager = FalsingManagerFake() + private lateinit var testableLooper: TestableLooper + private lateinit var tile: UiModeNightTile + private lateinit var configuration: Configuration + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + configuration = Configuration() + mContext.addMockSystemService(Context.UI_MODE_SERVICE, uiModeManager) + + `when`(qsHost.context).thenReturn(mockContext) + `when`(qsHost.userContext).thenReturn(mContext) + `when`(mockContext.resources).thenReturn(resources) + `when`(resources.configuration).thenReturn(configuration) + `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + + tile = UiModeNightTile( + qsHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + configurationController, + batteryController, + locationController) + } + + @Test + fun testIcon_whenNightModeOn_isOnState() { + val state = QSTile.BooleanState() + setNightModeOn() + + tile.handleUpdateState(state, /* arg= */ null) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on)) + } + + @Test + fun testIcon_whenNightModeOn_isOffState() { + val state = QSTile.BooleanState() + setNightModeOff() + + tile.handleUpdateState(state, /* arg= */ null) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off)) + } + + private fun setNightModeOn() { + `when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_YES) + configuration.uiMode = Configuration.UI_MODE_NIGHT_YES + } + + private fun setNightModeOff() { + `when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_NO) + configuration.uiMode = Configuration.UI_MODE_NIGHT_NO + } +} 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 d09a5a11040f..f92247580df0 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 @@ -143,6 +143,12 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void createInternetDialog_setAccessibilityPaneTitleToQuickSettings() { + assertThat(mDialogView.getAccessibilityPaneTitle()) + .isEqualTo(mContext.getText(R.string.accessibility_desc_quick_settings)); + } + + @Test public void hideWifiViews_WifiViewsGone() { mInternetDialog.hideWifiViews(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt index 00f38081c5c9..5a4bafcb7ded 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt @@ -23,7 +23,7 @@ import android.os.Binder import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.Display -import android.view.SurfaceControl.ScreenshotHardwareBuffer +import android.window.ScreenCapture.ScreenshotHardwareBuffer import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat @@ -84,7 +84,12 @@ class ImageCaptureImplTest : SysuiTestCase() { this.width = width this.height = height this.crop = crop - return ScreenshotHardwareBuffer(null, null, false, false) + return ScreenshotHardwareBuffer( + null, + null, + false, + false + ) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index e85ffb68de54..c4485389d646 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.testing.AndroidTestingRunner import android.view.DisplayCutout import android.view.View +import android.view.ViewPropertyAnimator import android.view.WindowInsets import android.widget.TextView import androidx.constraintlayout.motion.widget.MotionLayout @@ -30,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -64,6 +66,7 @@ import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyFloat import org.mockito.Mockito.anyInt @@ -614,6 +617,34 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { ) } + @Test + fun animateOutOnStartCustomizing() { + val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + controller.startCustomizingAnimation(show = true, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(0f) + verify(animator).setInterpolator(Interpolators.ALPHA_OUT) + verify(animator).start() + } + + @Test + fun animateInOnEndCustomizing() { + val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + controller.startCustomizingAnimation(show = false, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(1f) + verify(animator).setInterpolator(Interpolators.ALPHA_IN) + verify(animator).start() + } + private fun createWindowInsets( topCutout: Rect? = Rect() ): WindowInsets { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt index 8511443705e4..5ecfc8eb3649 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -4,10 +4,12 @@ import android.app.StatusBarManager import android.content.Context import android.testing.AndroidTestingRunner import android.view.View +import android.view.ViewPropertyAnimator import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -29,8 +31,10 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever @@ -198,4 +202,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { context.getString(com.android.internal.R.string.status_bar_alarm_clock) ) } + + @Test + fun animateOutOnStartCustomizing() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(0f) + verify(animator).setInterpolator(Interpolators.ALPHA_OUT) + verify(animator).start() + } + + @Test + fun animateInOnEndCustomizing() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(1f) + verify(animator).setInterpolator(Interpolators.ALPHA_IN) + verify(animator).start() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index f1c54ef8d6f7..b40d5ac69d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -183,6 +183,7 @@ import java.util.Optional; @TestableLooper.RunWithLooper public class NotificationPanelViewControllerTest extends SysuiTestCase { + private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50; private static final int PANEL_WIDTH = 500; // Random value just for the test. @@ -321,6 +322,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)) .thenReturn(10); + when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance)) + .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); when(mView.getContext()).thenReturn(getContext()); when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView); @@ -666,8 +669,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testSetPanelScrimMinFraction() { - mNotificationPanelViewController.setPanelScrimMinFraction(0.5f); + public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() { + mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( + mNotificationPanelViewController.getMaxPanelHeight() / 2); verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f)); } @@ -1018,7 +1022,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { FalsingManager.FalsingTapListener listener = getFalsingTapListener(); mStatusBarStateController.setState(KEYGUARD); - listener.onDoubleTapRequired(); + listener.onAdditionalTapRequired(); verify(mKeyguardIndicationController).showTransientIndication(anyInt()); } @@ -1028,7 +1032,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { FalsingManager.FalsingTapListener listener = getFalsingTapListener(); mStatusBarStateController.setState(SHADE_LOCKED); - listener.onDoubleTapRequired(); + listener.onAdditionalTapRequired(); verify(mTapAgainViewController).show(); } @@ -1363,40 +1367,47 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void getMaxPanelHeight_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() { - int splitShadeFullTransitionDistance = 123456; + public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() { + enableSplitShade(true); + mNotificationPanelViewController.expandWithQs(); + + int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); + + assertThat(maxDistance).isEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); + } + + @Test + public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() { enableSplitShade(true); - setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance); mNotificationPanelViewController.expandWithQs(); + when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true); + mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( + SPLIT_SHADE_FULL_TRANSITION_DISTANCE); - int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight(); + int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); - assertThat(maxPanelHeight).isEqualTo(splitShadeFullTransitionDistance); + assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); } @Test - public void getMaxPanelHeight_expandingSplitShade_keyguard_returnsNonSplitShadeValue() { + public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() { mStatusBarStateController.setState(KEYGUARD); - int splitShadeFullTransitionDistance = 123456; enableSplitShade(true); - setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance); mNotificationPanelViewController.expandWithQs(); - int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight(); + int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); - assertThat(maxPanelHeight).isNotEqualTo(splitShadeFullTransitionDistance); + assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); } @Test - public void getMaxPanelHeight_expanding_notSplitShade_returnsNonSplitShadeValue() { - int splitShadeFullTransitionDistance = 123456; + public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() { enableSplitShade(false); - setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance); mNotificationPanelViewController.expandWithQs(); - int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight(); + int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); - assertThat(maxPanelHeight).isNotEqualTo(splitShadeFullTransitionDistance); + assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); } @Test @@ -1543,12 +1554,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mTouchHandler.onTouch(mView, ev); } - private void setSplitShadeFullTransitionDistance(int splitShadeFullTransitionDistance) { - when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance)) - .thenReturn(splitShadeFullTransitionDistance); - mNotificationPanelViewController.updateResources(); - } - private void setDozing(boolean dozing, boolean dozingAlwaysOn) { when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn); mNotificationPanelViewController.setDozing( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt index 0c6a6a98052f..12ef036d89d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt @@ -20,6 +20,7 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.util.function.Consumer import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -33,10 +34,10 @@ import org.mockito.Mockito.doNothing import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.util.function.Consumer import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -63,6 +64,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Mock private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer @Mock + private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController + @Mock private lateinit var featureFlags: FeatureFlags @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> @@ -92,6 +95,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { notificationsQSContainer, navigationModeController, overviewProxyService, + largeScreenShadeHeaderController, featureFlags, delayableExecutor ) @@ -371,8 +375,14 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { container.removeAllViews() container.addView(newViewWithId(1)) container.addView(newViewWithId(View.NO_ID)) - val controller = NotificationsQSContainerController(container, navigationModeController, - overviewProxyService, featureFlags, delayableExecutor) + val controller = NotificationsQSContainerController( + container, + navigationModeController, + overviewProxyService, + largeScreenShadeHeaderController, + featureFlags, + delayableExecutor + ) controller.updateConstraints() assertThat(container.getChildAt(0).id).isEqualTo(1) @@ -397,6 +407,21 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM) } + @Test + fun testStartCustomizingWithDuration() { + controller.setCustomizerShowing(true, 100L) + verify(largeScreenShadeHeaderController).startCustomizingAnimation(true, 100L) + } + + @Test + fun testEndCustomizingWithDuration() { + controller.setCustomizerShowing(true, 0L) // Only tracks changes + reset(largeScreenShadeHeaderController) + + controller.setCustomizerShowing(false, 100L) + verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L) + } + private fun disableSplitShade() { setSplitShadeEnabled(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt index 97c0bb2c09ca..09add652483e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -89,7 +89,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_singleTapEnabled() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, prox not covered whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -100,7 +100,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN wake up device if dozing verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -108,7 +108,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_doubleTapEnabled() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, prox not covered whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -119,15 +119,27 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseDoubleTap).thenReturn(false) // WHEN there's a double tap - underTest.onDoubleTap(downEv) + underTest.onDoubleTapEvent(upEv) // THEN wake up device if dozing verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) } @Test + fun testGestureDetector_doubleTapEnabled_onDownEvent_noFalsingCheck() { + // GIVEN tap is enabled + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + + // WHEN there's a double tap on DOWN event + underTest.onDoubleTapEvent(downEv) + + // THEN don't check the falsing manager, should only be checked on the UP event + verify(falsingManager, never()).isFalseDoubleTap() + } + + @Test fun testGestureDetector_singleTapEnabled_falsing() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, prox not covered whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -138,29 +150,43 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) } @Test - fun testGestureDetector_notPulsing_noFalsingCheck() { - whenever(statusBarStateController.isPulsing).thenReturn(false) + fun testSingleTap_notDozing_noFalsingCheck() { + whenever(statusBarStateController.isDozing).thenReturn(false) - // GIVEN tap is enabled, prox not covered + // GIVEN tap is enabled whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) - // THEN the falsing manager never gets a call (because the device wasn't pulsing + // THEN the falsing manager never gets a call (because the device wasn't dozing + // during the tap) + verify(falsingManager, never()).isFalseTap(anyInt()) + } + + @Test + fun testDoubleTap_notDozing_noFalsingCheck() { + whenever(statusBarStateController.isDozing).thenReturn(false) + + // GIVEN tap is enabled + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + // WHEN there's a tap + underTest.onDoubleTapEvent(upEv) + + // THEN the falsing manager never gets a call (because the device wasn't dozing // during the tap) verify(falsingManager, never()).isFalseTap(anyInt()) } @Test fun testGestureDetector_doubleTapEnabled_falsing() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, prox not covered whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -170,8 +196,8 @@ class PulsingGestureListenerTest : SysuiTestCase() { // GIVEN the falsing manager thinks the tap is a false tap whenever(falsingManager.isFalseDoubleTap).thenReturn(true) - // WHEN there's a tap - underTest.onDoubleTap(downEv) + // WHEN there's a double tap ACTION_UP event + underTest.onDoubleTapEvent(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -179,7 +205,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_singleTapEnabled_proxCovered() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, not a false tap based on classifiers whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -190,7 +216,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isProximityNear()).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -198,7 +224,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_doubleTapEnabled_proxCovered() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, not a false tap based on classifiers whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -209,7 +235,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isProximityNear()).thenReturn(true) // WHEN there's a tap - underTest.onDoubleTap(downEv) + underTest.onDoubleTapEvent(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -227,3 +253,4 @@ class PulsingGestureListenerTest : SysuiTestCase() { } private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) +private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt index baaa447d53a3..6be76a6ac969 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt @@ -13,6 +13,7 @@ import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.HeadsUpManager import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -28,6 +29,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() { @Mock private lateinit var scrimController: ScrimController @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var headsUpManager: HeadsUpManager private val configurationController = FakeConfigurationController() private lateinit var controller: ScrimShadeTransitionController @@ -42,7 +44,8 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() { dumpManager, scrimController, context.resources, - statusBarStateController) + statusBarStateController, + headsUpManager) controller.onPanelStateChanged(STATE_OPENING) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 74e274705e55..464dfe26ca3e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -19,8 +19,10 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; @@ -86,6 +88,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; @@ -167,6 +170,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock + private FaceHelpMessageDeferral mFaceHelpMessageDeferral; + @Mock private ScreenLifecycle mScreenLifecycle; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @@ -259,7 +264,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils, - mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager); + mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, + mFaceHelpMessageDeferral); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); @@ -535,7 +541,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.setVisible(true); mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); @@ -582,7 +588,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN there's a face not recognized message mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); @@ -748,8 +754,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(false); - // WHEN help message received + // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -777,8 +785,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); - // WHEN help message received + // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -1126,7 +1136,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen); } - @Test public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() { // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled @@ -1272,6 +1281,87 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen); } + @Test + public void faceOnAcquired_processFrame() { + createController(); + + // WHEN face sends an acquired message + final int acquireInfo = 1; + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo); + + // THEN face help message deferral should process the acquired frame + verify(mFaceHelpMessageDeferral).processFrame(acquireInfo); + } + + @Test + public void fingerprintOnAcquired_noProcessFrame() { + createController(); + + // WHEN fingerprint sends an acquired message + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1); + + // THEN face help message deferral should NOT process any acquired frames + verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt()); + } + + @Test + public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() { + createController(); + + // WHEN fingerprint sends an onBiometricHelp + mKeyguardUpdateMonitorCallback.onBiometricHelp( + 1, + "placeholder", + BiometricSourceType.FINGERPRINT); + + // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString()); + verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt()); + } + + @Test + public void onBiometricFailed_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricError_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face has an error + mKeyguardUpdateMonitorCallback.onBiometricError(4, "string", + BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + final int msgId = 1; + final String helpString = "test"; + mKeyguardUpdateMonitorCallback.onBiometricHelp( + msgId, + "test", + BiometricSourceType.FACE); + + // THEN face help message deferral is NOT reset and message IS updated + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString); + } + + + private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 14b74712ec43..f510e48de5a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -69,7 +70,11 @@ import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewRootImpl; import android.view.WindowManager; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; +import android.window.WindowOnBackInvokedDispatcher; import androidx.test.filters.SmallTest; @@ -168,6 +173,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -279,6 +285,15 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private InteractionJankMonitor mJankMonitor; @Mock private DeviceStateManager mDeviceStateManager; @Mock private WiredChargingRippleController mWiredChargingRippleController; + /** + * The process of registering/unregistering a predictive back callback requires a + * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. + * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl. + */ + @Mock private ViewRootImpl mViewRootImpl; + @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; + @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; + private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -368,10 +383,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return null; }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); - mShadeController = new ShadeControllerImpl(mCommandQueue, + mShadeController = spy(new ShadeControllerImpl(mCommandQueue, mStatusBarStateController, mNotificationShadeWindowController, mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class), - () -> Optional.of(mCentralSurfaces), () -> mAssistManager); + () -> Optional.of(mCentralSurfaces), () -> mAssistManager)); when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); @@ -460,7 +475,14 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mActivityLaunchAnimator, mJankMonitor, mDeviceStateManager, - mWiredChargingRippleController, mDreamManager); + mWiredChargingRippleController, mDreamManager) { + @Override + protected ViewRootImpl getViewRootImpl() { + return mViewRootImpl; + } + }; + when(mViewRootImpl.getOnBackInvokedDispatcher()) + .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( any(CentralSurfacesImpl.class), any(NotificationPanelViewController.class), @@ -738,6 +760,43 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } } + /** + * Do the following: + * 1. verify that a predictive back callback is registered when CSurf becomes visible + * 2. verify that the same callback is unregistered when CSurf becomes invisible + */ + @Test + public void testPredictiveBackCallback_registration() { + mCentralSurfaces.handleVisibleToUserChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), + mOnBackInvokedCallback.capture()); + + mCentralSurfaces.handleVisibleToUserChanged(false); + verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( + eq(mOnBackInvokedCallback.getValue())); + } + + /** + * Do the following: + * 1. capture the predictive back callback during registration + * 2. call the callback directly + * 3. verify that the ShadeController's panel collapse animation is invoked + */ + @Test + public void testPredictiveBackCallback_invocationCollapsesPanel() { + mCentralSurfaces.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); + mCentralSurfaces.handleVisibleToUserChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), + mOnBackInvokedCallback.capture()); + + when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); + mOnBackInvokedCallback.getValue().onBackInvoked(); + verify(mShadeController).animateCollapsePanels(); + } + @Test public void testPanelOpenForHeadsUp() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index 60a3d95e24f6..ab209d130b2f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -408,6 +408,16 @@ public class KeyguardBouncerTest extends SysuiTestCase { mBouncer.hide(false /* destroyView */); verify(mHandler).removeCallbacks(eq(showRunnable.getValue())); } + + @Test + public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() { + when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); + when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true); + mBouncer.show(true /* reset */); + + verify(mHandler, never()).postDelayed(any(), anyLong()); + } + @Test public void testShow_delaysIfFaceAuthIsRunning_unlessBypassEnabled() { when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 3dd36d134cf7..d0391ac0795c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -41,6 +41,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.SysuiTestCase; +import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; import org.junit.Before; @@ -81,6 +82,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl = new BluetoothControllerImpl(mContext, mMockDumpManager, + mock(BluetoothLogger.class), mTestableLooper.getLooper(), mTestableLooper.getLooper(), mMockBluetoothManager); @@ -233,4 +235,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); } + + /** Regression test for b/246876230. */ + @Test + public void testOnActiveDeviceChanged_null_noCrash() { + mBluetoothControllerImpl.onActiveDeviceChanged(null, BluetoothProfile.HEADSET); + // No assert, just need no crash. + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index e616c26377d2..921b7efc38eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -17,20 +17,14 @@ package com.android.systemui.temporarydisplay import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable import android.os.PowerManager -import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager -import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.android.systemui.util.concurrency.DelayableExecutor @@ -42,9 +36,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -58,13 +50,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor - private lateinit var appIconFromPackageName: Drawable @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var applicationInfo: ApplicationInfo - @Mock - private lateinit var logger: MediaTttLogger + private lateinit var logger: TemporaryViewLogger @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock @@ -78,17 +65,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!! - whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName) - whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) - whenever(packageManager.getApplicationInfo( - any(), any<PackageManager.ApplicationInfoFlags>() - )).thenThrow(PackageManager.NameNotFoundException()) - whenever(packageManager.getApplicationInfo( - eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() - )).thenReturn(applicationInfo) - context.setMockPackageManager(packageManager) - whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())) .thenReturn(TIMEOUT_MS.toInt()) @@ -229,117 +205,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } - @Test - fun setIcon_nullAppIconDrawableAndNullPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appIconDrawableOverride = null) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawableAndInvalidPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appIconDrawableOverride = null - ) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawable_iconIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, appIconDrawableOverride = null, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(appIconFromPackageName) - } - - @Test - fun setIcon_hasAppIconDrawable_iconIsDrawable() { - underTest.displayView(getState()) - val view = getView() - - val drawable = context.getDrawable(R.drawable.ic_alarm)!! - underTest.setIcon(view, PACKAGE_NAME, drawable, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(drawable) - } - - @Test - fun setIcon_nullAppNameAndNullPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppNameAndInvalidPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appNameOverride = null - ) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppName_iconContentDescriptionIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) - } - - @Test - fun setIcon_hasAppName_iconContentDescriptionIsAppNameOverride() { - underTest.displayView(getState()) - val view = getView() - - val appName = "Override App Name" - underTest.setIcon(view, PACKAGE_NAME, null, appName) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(appName) - } - - @Test - fun setIcon_iconSizeMatchesGetIconSize() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME) - view.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) - ) - - assertThat(view.getAppIconView().measuredWidth).isEqualTo(ICON_SIZE) - assertThat(view.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE) - } - private fun getState(name: String = "name") = ViewInfo(name) - private fun getView(): ViewGroup { - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(windowManager).addView(viewCaptor.capture(), any()) - return viewCaptor.value as ViewGroup - } - - private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) - private fun getConfigurationListener(): ConfigurationListener { val callbackCaptor = argumentCaptor<ConfigurationListener>() verify(configurationController).addCallback(capture(callbackCaptor)) @@ -348,13 +215,13 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { inner class TestController( context: Context, - logger: MediaTttLogger, + logger: TemporaryViewLogger, windowManager: WindowManager, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, powerManager: PowerManager, - ) : TemporaryViewDisplayController<ViewInfo>( + ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>( context, logger, windowManager, @@ -363,6 +230,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { configurationController, powerManager, R.layout.media_ttt_chip, + "Window Title", + "WAKE_REASON", ) { var mostRecentViewInfo: ViewInfo? = null @@ -371,7 +240,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { super.updateView(newInfo, currentView) mostRecentViewInfo = newInfo } - override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE } inner class ViewInfo(val name: String) : TemporaryViewInfo { @@ -379,7 +247,4 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } } -private const val PACKAGE_NAME = "com.android.systemui" -private const val APP_NAME = "Fake App Name" private const val TIMEOUT_MS = 10000L -private const val ICON_SIZE = 47 diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt new file mode 100644 index 000000000000..c9f2b4db81ef --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -0,0 +1,70 @@ +/* + * 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.temporarydisplay + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.LogcatEchoTracker +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito + +@SmallTest +class TemporaryViewLoggerTest : SysuiTestCase() { + private lateinit var buffer: LogBuffer + private lateinit var logger: TemporaryViewLogger + + @Before + fun setUp() { + buffer = + LogBufferFactory(DumpManager(), Mockito.mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + logger = TemporaryViewLogger(buffer, TAG) + } + + @Test + fun logChipAddition_bufferHasLog() { + logger.logChipAddition() + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(TAG) + } + + @Test + fun logChipRemoval_bufferHasTagAndReason() { + val reason = "test reason" + logger.logChipRemoval(reason) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(TAG) + assertThat(actualString).contains(reason) + } +} + +private const val TAG = "TestTag" diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt index 439beaab6c7e..3968bb798bb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt @@ -16,11 +16,17 @@ package com.android.systemui.user +import android.app.Application import android.os.UserManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater +import android.view.View +import android.view.Window +import android.window.OnBackInvokedCallback +import android.window.OnBackInvokedDispatcher import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.classifier.FalsingCollector @@ -29,12 +35,25 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.concurrent.Executor @SmallTest @RunWith(AndroidTestingRunner::class) @@ -60,11 +79,22 @@ class UserSwitcherActivityTest : SysuiTestCase() { private lateinit var flags: FeatureFlags @Mock private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory> + @Mock + private lateinit var onBackDispatcher: OnBackInvokedDispatcher + @Mock + private lateinit var decorView: View + @Mock + private lateinit var window: Window + @Mock + private lateinit var userSwitcherRootView: UserSwitcherRootView + @Captor + private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback> + var isFinished = false @Before fun setUp() { MockitoAnnotations.initMocks(this) - activity = UserSwitcherActivity( + activity = spy(object : UserSwitcherActivity( userSwitcherController, broadcastDispatcher, falsingCollector, @@ -73,7 +103,21 @@ class UserSwitcherActivityTest : SysuiTestCase() { userTracker, flags, viewModelFactoryLazy, - ) + ) { + override fun getOnBackInvokedDispatcher() = onBackDispatcher + override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock()) + override fun finish() { + isFinished = true + } + }) + `when`(activity.window).thenReturn(window) + `when`(window.decorView).thenReturn(decorView) + `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root)) + .thenReturn(userSwitcherRootView) + `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java)) + `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java)) + `when`(activity.application).thenReturn(mock(Application::class.java)) + doNothing().`when`(activity).setContentView(anyInt()) } @Test @@ -85,4 +129,24 @@ class UserSwitcherActivityTest : SysuiTestCase() { assertThat(activity.getMaxColumns(7)).isEqualTo(4) assertThat(activity.getMaxColumns(9)).isEqualTo(5) } + + @Test + fun onCreate_callbackRegistration() { + activity.createActivity() + verify(onBackDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()) + + activity.destroyActivity() + verify(onBackDispatcher).unregisterOnBackInvokedCallback(any()) + } + + @Test + fun onBackInvokedCallback_finishesActivity() { + activity.createActivity() + verify(onBackDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture()) + + onBackInvokedCallback.value.onBackInvoked() + assertThat(isFinished).isTrue() + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 3324c526ecc2..799c759b96d2 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -75,7 +75,6 @@ import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; -import android.view.SurfaceControl.ScreenshotHardwareBuffer; import android.view.View; import android.view.WindowInfo; import android.view.accessibility.AccessibilityCache; @@ -84,6 +83,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.inputmethod.EditorInfo; +import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 0bba3c9cba6b..9f27f721ea83 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -150,8 +150,9 @@ public class SystemDataTransferProcessor { // Create a PendingIntent final long token = Binder.clearCallingIdentity(); try { - return PendingIntent.getActivity(mContext, /*requestCode */ associationId, intent, - FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); + return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent, + FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null, + UserHandle.CURRENT); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 7ca62542aaca..9f3f761eac65 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -577,6 +577,14 @@ public abstract class PackageManagerInternal { int filterCallingUid); /** + * Resolves an exported activity intent, allowing instant apps to be resolved. + */ + public abstract ResolveInfo resolveIntentExported(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, + @PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart, + int filterCallingUid); + + /** * Resolves a service intent, allowing instant apps to be resolved. */ public abstract ResolveInfo resolveService(Intent intent, String resolvedType, diff --git a/services/core/java/com/android/server/HardwarePropertiesManagerService.java b/services/core/java/com/android/server/HardwarePropertiesManagerService.java index e21a3d7917d1..6b3f5e23cbd9 100644 --- a/services/core/java/com/android/server/HardwarePropertiesManagerService.java +++ b/services/core/java/com/android/server/HardwarePropertiesManagerService.java @@ -98,12 +98,17 @@ public class HardwarePropertiesManagerService extends IHardwarePropertiesManager } private String getCallingPackageName() { - final String[] packages = mContext.getPackageManager().getPackagesForUid( - Binder.getCallingUid()); + final PackageManager pm = mContext.getPackageManager(); + final int uid = Binder.getCallingUid(); + final String[] packages = pm.getPackagesForUid(uid); if (packages != null && packages.length > 0) { return packages[0]; } - return "unknown"; + final String name = pm.getNameForUid(uid); + if (name != null) { + return name; + } + return String.valueOf(uid); } private void dumpTempValues(String pkg, PrintWriter pw, int type, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6e52d21e6b43..1b68aba7eecf 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -215,6 +215,7 @@ import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManagerInternal; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.AutofillOptions; import android.content.BroadcastReceiver; @@ -585,6 +586,17 @@ public class ActivityManagerService extends IActivityManager.Stub private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L; /** + * Apps targeting Android U and above will need to export components in order to invoke them + * through implicit intents. + * + * If a component is not exported and invoked, it will be removed from the list of receivers. + * This applies specifically to activities and broadcasts. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + public static final long IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS = 229362273; + + /** * The maximum number of bytes that {@link #setProcessStateSummary} accepts. * * @see {@link android.app.ActivityManager#setProcessStateSummary(byte[])} @@ -12379,6 +12391,58 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Filters out non-exported components in a given list of broadcast filters + * @param intent the original intent + * @param callingUid the calling UID + * @param query the list of broadcast filters + * @param platformCompat the instance of platform compat + */ + private static void filterNonExportedComponents(Intent intent, int callingUid, + List query, PlatformCompat platformCompat, String callerPackage) { + if (query == null + || intent.getPackage() != null + || intent.getComponent() != null + || ActivityManager.canAccessUnexportedComponents(callingUid)) { + return; + } + for (int i = query.size() - 1; i >= 0; i--) { + String componentInfo; + ResolveInfo resolveInfo; + BroadcastFilter broadcastFilter; + if (query.get(i) instanceof ResolveInfo) { + resolveInfo = (ResolveInfo) query.get(i); + if (resolveInfo.getComponentInfo().exported) { + continue; + } + componentInfo = resolveInfo.getComponentInfo() + .getComponentName().flattenToShortString(); + } else if (query.get(i) instanceof BroadcastFilter) { + broadcastFilter = (BroadcastFilter) query.get(i); + if (broadcastFilter.exported) { + continue; + } + componentInfo = broadcastFilter.packageName; + } else { + continue; + } + if (!platformCompat.isChangeEnabledByUid( + IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS, callingUid)) { + Slog.w(TAG, "Non-exported component not filtered out " + + "(will be filtered out once the app targets U+)- intent: " + + intent.getAction() + ", component: " + + componentInfo + ", sender: " + + callerPackage); + return; + } + Slog.w(TAG, "Non-exported component filtered out - intent: " + + intent.getAction() + ", component: " + + componentInfo + ", sender: " + + callerPackage); + query.remove(i); + } + } + + /** * Main code for cleaning up a process when it has gone away. This is * called both as a result of the process dying, or directly when stopping * a process when running in single process mode. @@ -14217,6 +14281,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } + filterNonExportedComponents(intent, callingUid, registeredReceivers, + mPlatformCompat, callerPackage); int NR = registeredReceivers != null ? registeredReceivers.size() : 0; if (!ordered && NR > 0) { // If we are not serializing this broadcast, then send the @@ -14319,6 +14385,8 @@ public class ActivityManagerService extends IActivityManager.Stub if ((receivers != null && receivers.size() > 0) || resultTo != null) { BroadcastQueue queue = broadcastQueueForIntent(intent); + filterNonExportedComponents(intent, callingUid, receivers, + mPlatformCompat, callerPackage); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions, @@ -16396,11 +16464,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - @Override - public void enableBinderTracing() { - Binder.enableTracingForUid(Binder.getCallingUid()); - } - @VisibleForTesting public final class LocalService extends ActivityManagerInternal implements ActivityManagerLocal { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 1954f76825cd..6775c9952ddd 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -376,6 +376,11 @@ class UserController implements Handler.Callback { private boolean mDelayUserDataLocking; /** + * Users are only allowed to be unlocked after boot complete. + */ + private volatile boolean mAllowUserUnlocking; + + /** * Keep track of last active users for mDelayUserDataLocking. * The latest stopped user is placed in front while the least recently stopped user in back. */ @@ -434,6 +439,11 @@ class UserController implements Handler.Callback { mUserLru.add(UserHandle.USER_SYSTEM); mLockPatternUtils = mInjector.getLockPatternUtils(); updateStartedUserArrayLU(); + + // TODO(b/232452368): currently mAllowUserUnlocking is only used on devices with HSUM + // (Headless System User Mode), but on master it will be used by all devices (and hence this + // initial assignment should be removed). + mAllowUserUnlocking = !UserManager.isHeadlessSystemUserMode(); } void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers, @@ -1801,6 +1811,22 @@ class UserController implements Handler.Callback { private boolean unlockUserCleared(final @UserIdInt int userId, byte[] secret, IProgressListener listener) { + // Delay user unlocking for headless system user mode until the system boot + // completes. When the system boot completes, the {@link #onBootCompleted()} + // method unlocks all started users for headless system user mode. This is done + // to prevent unlocking the users too early during the system boot up. + // Otherwise, emulated volumes are mounted too early during the system + // boot up. When vold is reset on boot complete, vold kills all apps/services + // (that use these emulated volumes) before unmounting the volumes(b/241929666). + // In the past, these killings have caused the system to become too unstable on + // some occasions. + // Any unlocks that get delayed by this will be done by onBootComplete() instead. + if (!mAllowUserUnlocking) { + Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId); + notifyFinished(userId, listener); + return false; + } + UserState uss; if (!StorageManager.isUserKeyUnlocked(userId)) { final UserInfo userInfo = getUserInfo(userId); @@ -2397,7 +2423,44 @@ class UserController implements Handler.Callback { } } + @VisibleForTesting + void setAllowUserUnlocking(boolean allowed) { + mAllowUserUnlocking = allowed; + if (DEBUG_MU) { + // TODO(b/245335748): use Slogf.d instead + // Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed); + android.util.Slog.d(TAG, "setAllowUserUnlocking():" + allowed, new Exception()); + } + } + + /** + * @deprecated TODO(b/232452368): this logic will be merged into sendBootCompleted + */ + @Deprecated + private void onBootCompletedOnHeadlessSystemUserModeDevices() { + setAllowUserUnlocking(true); + + // Get a copy of mStartedUsers to use outside of lock. + SparseArray<UserState> startedUsers; + synchronized (mLock) { + startedUsers = mStartedUsers.clone(); + } + // USER_SYSTEM must be processed first. It will be first in the array, as its ID is lowest. + Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM); + for (int i = 0; i < startedUsers.size(); i++) { + UserState uss = startedUsers.valueAt(i); + int userId = uss.mHandle.getIdentifier(); + Slogf.i(TAG, "Attempting to unlock user %d on boot complete", userId); + maybeUnlockUser(userId); + } + } + void sendBootCompleted(IIntentReceiver resultTo) { + if (UserManager.isHeadlessSystemUserMode()) { + // Unlocking users is delayed until boot complete for headless system user mode. + onBootCompletedOnHeadlessSystemUserModeDevices(); + } + // Get a copy of mStartedUsers to use outside of lock SparseArray<UserState> startedUsers; synchronized (mLock) { @@ -2848,6 +2911,7 @@ class UserController implements Handler.Callback { pw.println(" mTargetUserId:" + mTargetUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); + pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking); pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 0f5aa3a08950..4aaf1abf4147 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -52,6 +52,7 @@ import android.util.Slog; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.WindowManager; +import android.window.ScreenCapture; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -843,8 +844,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan final SurfaceControl overlaySurfaceControl = overlaySurfacePackage != null ? overlaySurfacePackage.getSurfaceControl() : null; mBackgroundExecutor.execute(() -> { - final SurfaceControl.LayerCaptureArgs.Builder layerCaptureArgsBuilder = - new SurfaceControl.LayerCaptureArgs.Builder(/* layer */ null); + final ScreenCapture.LayerCaptureArgs.Builder layerCaptureArgsBuilder = + new ScreenCapture.LayerCaptureArgs.Builder(/* layer */ null); if (overlaySurfaceControl != null) { SurfaceControl[] excludeLayers = new SurfaceControl[1]; excludeLayers[0] = overlaySurfaceControl; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 62ae9b8a9789..4a7e63162747 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -16,9 +16,6 @@ package com.android.server.appop; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; @@ -46,6 +43,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; +import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; @@ -56,13 +54,6 @@ import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; -import static android.app.AppOpsManager.UID_STATE_BACKGROUND; -import static android.app.AppOpsManager.UID_STATE_CACHED; -import static android.app.AppOpsManager.UID_STATE_FOREGROUND; -import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; -import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; -import static android.app.AppOpsManager.UID_STATE_PERSISTENT; -import static android.app.AppOpsManager.UID_STATE_TOP; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; @@ -72,7 +63,6 @@ import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; -import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; @@ -80,8 +70,6 @@ import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; -import static java.lang.Long.max; - import android.Manifest; import android.annotation.IntRange; import android.annotation.NonNull; @@ -167,6 +155,7 @@ import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.os.Clock; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -236,39 +225,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch // Constant meaning that any UID should be matched when dispatching callbacks private static final int UID_ANY = -2; - // Map from process states to the uid states we track. - private static final int[] PROCESS_STATE_TO_UID_STATE = new int[] { - UID_STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT - UID_STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - UID_STATE_TOP, // ActivityManager.PROCESS_STATE_TOP - UID_STATE_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_TOP - UID_STATE_FOREGROUND_SERVICE, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - UID_STATE_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_BACKUP - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_SERVICE - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_RECEIVER - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_HOME - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_NONEXISTENT - }; - private static final int[] OPS_RESTRICTED_ON_SUSPEND = { OP_PLAY_AUDIO, OP_RECORD_AUDIO, OP_CAMERA, - }; - - private static final int[] WATCHABLE_NON_PERMISSION_OPS = { - OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + OP_VIBRATE, }; private static final int MAX_UNFORWARDED_OPS = 10; @@ -346,8 +307,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); - long mLastRealtime; - /* * These are app op restrictions imposed per user from various parties. */ @@ -408,6 +367,21 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** Interface for app-op modes.*/ @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface; + private AppOpsUidStateTracker mUidStateTracker; + + /** Hands the definition of foreground and uid states */ + @GuardedBy("this") + public AppOpsUidStateTracker getUidStateTracker() { + if (mUidStateTracker == null) { + mUidStateTracker = new AppOpsUidStateTrackerImpl( + LocalServices.getService(ActivityManagerInternal.class), mHandler, + Clock.SYSTEM_CLOCK, mConstants); + + mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged); + } + return mUidStateTracker; + } + /** * An unsynchronized pool of {@link OpEventProxyInfo} objects. */ @@ -467,7 +441,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch * global Settings. Any access to this class or its fields should be done while * holding the AppOpsService lock. */ - @VisibleForTesting final class Constants extends ContentObserver { /** @@ -555,14 +528,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final class UidState { public final int uid; - public int state = UID_STATE_CACHED; - public int pendingState = UID_STATE_CACHED; - public long pendingStateCommitTime; - public int capability; - public int pendingCapability; - public boolean appWidgetVisible; - public boolean pendingAppWidgetVisible; - public ArrayMap<String, Ops> pkgOps; // true indicates there is an interested observer, false there isn't but it has such an op @@ -596,10 +561,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } return (pkgOps == null || pkgOps.isEmpty()) - && (state == UID_STATE_CACHED - && (pendingState == UID_STATE_CACHED)) - && (mAppOpsServiceInterface.areUidModesDefault(uid) - && areAllPackageModesDefault); + && mAppOpsServiceInterface.areUidModesDefault(uid) + && areAllPackageModesDefault; } // Functions for uid mode access and manipulation. @@ -615,52 +578,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mAppOpsServiceInterface.setUidMode(uid, op, mode); } + @SuppressWarnings("GuardedBy") int evalMode(int op, int mode) { - if (mode == MODE_FOREGROUND) { - if (appWidgetVisible) { - return MODE_ALLOWED; - } else if (mActivityManagerInternal != null - && mActivityManagerInternal.isPendingTopUid(uid)) { - return MODE_ALLOWED; - } else if (mActivityManagerInternal != null - && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) { - return MODE_ALLOWED; - } else if (state <= UID_STATE_TOP) { - // process is in TOP. - return MODE_ALLOWED; - } else if (state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)) { - // process is in foreground, check its capability. - switch (op) { - case AppOpsManager.OP_FINE_LOCATION: - case AppOpsManager.OP_COARSE_LOCATION: - case AppOpsManager.OP_MONITOR_LOCATION: - case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: - if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - case OP_CAMERA: - if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - case OP_RECORD_AUDIO: - if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - default: - return MODE_ALLOWED; - } - } else { - // process is not in foreground. - return MODE_IGNORED; - } - } - return mode; + return getUidStateTracker().evalMode(uid, op, mode); } public void evalForegroundOps() { @@ -683,6 +603,16 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } } + + @SuppressWarnings("GuardedBy") + public int getState() { + return getUidStateTracker().getUidState(uid); + } + + @SuppressWarnings("GuardedBy") + public void dump(PrintWriter pw, long nowElapsed) { + getUidStateTracker().dumpUidState(pw, uid, nowElapsed); + } } final static class Ops extends SparseArray<Op> { @@ -710,7 +640,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */ + /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */ private static final class PackageVerificationResult { final RestrictionBypass bypass; @@ -1481,10 +1411,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch UserHandle.getUserId(this.uid)); } - int evalMode() { - return uidState.evalMode(op, getMode()); - } - void removeAttributionsWithNoTime() { for (int i = mAttributions.size() - 1; i >= 0; i--) { if (!mAttributions.valueAt(i).hasAnyTime()) { @@ -2096,75 +2022,68 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** - * Update the pending state for the uid - * - * @param currentTime The current elapsed real time - * @param uid The uid that has a pending state - */ - private void updatePendingState(long currentTime, int uid) { + // The callback method from ForegroundPolicyInterface + private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { synchronized (this) { - mLastRealtime = max(currentTime, mLastRealtime); - updatePendingStateIfNeededLocked(mUidStates.get(uid)); - } - } + UidState uidState = getUidStateLocked(uid, true); - public void updateUidProcState(int uid, int procState, - @ActivityManager.ProcessCapability int capability) { - synchronized (this) { - final UidState uidState = getUidStateLocked(uid, true); - final int newState = PROCESS_STATE_TO_UID_STATE[procState]; - if (uidState != null && (uidState.pendingState != newState - || uidState.pendingCapability != capability)) { - final int oldPendingState = uidState.pendingState; - uidState.pendingState = newState; - uidState.pendingCapability = capability; - if (newState < uidState.state - || (newState <= UID_STATE_MAX_LAST_NON_RESTRICTED - && uidState.state > UID_STATE_MAX_LAST_NON_RESTRICTED)) { - // We are moving to a more important state, or the new state may be in the - // foreground and the old state is in the background, then always do it - // immediately. - commitUidPendingStateLocked(uidState); - } else if (newState == uidState.state && capability != uidState.capability) { - // No change on process state, but process capability has changed. - commitUidPendingStateLocked(uidState); - } else if (uidState.pendingStateCommitTime == 0) { - // We are moving to a less important state for the first time, - // delay the application for a bit. - final long settleTime; - if (uidState.state <= UID_STATE_TOP) { - settleTime = mConstants.TOP_STATE_SETTLE_TIME; - } else if (uidState.state <= UID_STATE_FOREGROUND_SERVICE) { - settleTime = mConstants.FG_SERVICE_STATE_SETTLE_TIME; - } else { - settleTime = mConstants.BG_STATE_SETTLE_TIME; + if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) { + for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { + if (!uidState.foregroundOps.valueAt(fgi)) { + continue; } - final long commitTime = SystemClock.elapsedRealtime() + settleTime; - uidState.pendingStateCommitTime = commitTime; + final int code = uidState.foregroundOps.keyAt(fgi); - mHandler.sendMessageDelayed( - PooledLambda.obtainMessage(AppOpsService::updatePendingState, this, - commitTime + 1, uid), settleTime + 1); + if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) + && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChangedForAllPkgsInUid, + this, code, uidState.uid, true, null)); + } else if (uidState.pkgOps != null) { + final ArraySet<OnOpModeChangedListener> listenerSet = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { + final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); + if ((listener.getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !listener.isWatchingUid(uidState.uid)) { + continue; + } + for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { + final Op op = uidState.pkgOps.valueAt(pkgi).get(code); + if (op == null) { + continue; + } + if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChanged, + this, listenerSet.valueAt(cbi), code, uidState.uid, + uidState.pkgOps.keyAt(pkgi))); + } + } + } + } + } } + } - if (uidState.pkgOps != null) { - int numPkgs = uidState.pkgOps.size(); - for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { - Ops ops = uidState.pkgOps.valueAt(pkgNum); + if (uidState != null && uidState.pkgOps != null) { + int numPkgs = uidState.pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + Ops ops = uidState.pkgOps.valueAt(pkgNum); - int numOps = ops.size(); - for (int opNum = 0; opNum < numOps; opNum++) { - Op op = ops.valueAt(opNum); + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); - int numAttributions = op.mAttributions.size(); - for (int attributionNum = 0; attributionNum < numAttributions; - attributionNum++) { - AttributedOp attributedOp = op.mAttributions.valueAt( - attributionNum); + int numAttributions = op.mAttributions.size(); + for (int attributionNum = 0; attributionNum < numAttributions; + attributionNum++) { + AttributedOp attributedOp = op.mAttributions.valueAt( + attributionNum); - attributedOp.onUidStateChanged(newState); - } + attributedOp.onUidStateChanged(state); } } } @@ -2172,6 +2091,22 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } + /** + * Notify the proc state or capability has changed for a certain UID. + */ + public void updateUidProcState(int uid, int procState, + @ActivityManager.ProcessCapability int capability) { + synchronized (this) { + getUidStateTracker().updateUidProcState(uid, procState, capability); + if (!mUidStates.contains(uid)) { + UidState uidState = new UidState(uid); + mUidStates.put(uid, uidState); + onUidStateChanged(uid, + AppOpsUidStateTracker.processStateToUidState(procState), false); + } + } + } + public void shutdown() { Slog.w(TAG, "Writing app ops before shutdown..."); boolean doWrite = false; @@ -3162,7 +3097,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (op == null) { return AppOpsManager.opToDefaultMode(code); } - return raw ? op.getMode() : op.evalMode(); + return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode()); } } @@ -3380,7 +3315,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final int switchCode = AppOpsManager.opToSwitch(code); final UidState uidState = ops.uidState; if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) { - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_IGNORED); return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, @@ -3394,7 +3329,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode); return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); @@ -3402,12 +3337,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else { final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) : op; - final int mode = switchOp.evalMode(); + final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); if (mode != AppOpsManager.MODE_ALLOWED) { if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode); return new SyncNotedAppOp(mode, code, attributionTag, packageName); @@ -3422,7 +3357,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_ALLOWED); - attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, uidState.state, + attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, + uidState.getState(), flags); if (shouldCollectAsyncNotedOp) { @@ -3913,7 +3849,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch + packageName + " flags: " + AppOpsManager.flagsToString(flags)); } if (!dryRun) { - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode, startType, attributionFlags, attributionChainId); } @@ -3922,14 +3858,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else { final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) : op; - final int mode = switchOp.evalMode(); + final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); if (mode != AppOpsManager.MODE_ALLOWED && (!startIfModeDefault || mode != MODE_DEFAULT)) { if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " + packageName + " flags: " + AppOpsManager.flagsToString(flags)); if (!dryRun) { - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode, startType, attributionFlags, attributionChainId); } @@ -3943,12 +3879,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch try { if (isRestricted) { attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.state, flags, attributionFlags, - attributionChainId); + proxyAttributionTag, uidState.getState(), flags, + attributionFlags, attributionChainId); } else { attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.state, flags, attributionFlags, - attributionChainId); + proxyAttributionTag, uidState.getState(), flags, + attributionFlags, attributionChainId); startType = START_TYPE_STARTED; } } catch (RemoteException e) { @@ -4252,7 +4188,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public boolean shouldCollectNotes(int opCode) { Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode"); - if (ArrayUtils.contains(WATCHABLE_NON_PERMISSION_OPS, opCode)) { + if (AppOpsManager.shouldForceCollectNoteForOp(opCode)) { return true; } @@ -4362,101 +4298,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } uidState = new UidState(uid); mUidStates.put(uid, uidState); - } else { - updatePendingStateIfNeededLocked(uidState); } - return uidState; - } - /** - * Check if the pending state should be updated and do so if needed - * - * @param uidState The uidState that might have a pending state - */ - private void updatePendingStateIfNeededLocked(@NonNull UidState uidState) { - if (uidState != null) { - if (uidState.pendingStateCommitTime != 0) { - if (uidState.pendingStateCommitTime < mLastRealtime) { - commitUidPendingStateLocked(uidState); - } else { - mLastRealtime = SystemClock.elapsedRealtime(); - if (uidState.pendingStateCommitTime < mLastRealtime) { - commitUidPendingStateLocked(uidState); - } - } - } - } - } - - private void commitUidPendingStateLocked(UidState uidState) { - if (uidState.hasForegroundWatchers) { - for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { - if (!uidState.foregroundOps.valueAt(fgi)) { - continue; - } - final int code = uidState.foregroundOps.keyAt(fgi); - // For location ops we consider fg state only if the fg service - // is of location type, for all other ops any fg service will do. - final long firstUnrestrictedUidState = resolveFirstUnrestrictedUidState(code); - final boolean resolvedLastFg = uidState.state <= firstUnrestrictedUidState; - final boolean resolvedNowFg = uidState.pendingState <= firstUnrestrictedUidState; - if (resolvedLastFg == resolvedNowFg - && uidState.capability == uidState.pendingCapability - && uidState.appWidgetVisible == uidState.pendingAppWidgetVisible) { - continue; - } - - if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) - && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChangedForAllPkgsInUid, - this, code, uidState.uid, true, null)); - } else if (uidState.pkgOps != null) { - final ArraySet<OnOpModeChangedListener> listenerSet = - mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (listenerSet != null) { - for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { - final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); - if ((listener.getFlags() - & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !listener.isWatchingUid(uidState.uid)) { - continue; - } - for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { - final Op op = uidState.pkgOps.valueAt(pkgi).get(code); - if (op == null) { - continue; - } - if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, listenerSet.valueAt(cbi), code, uidState.uid, - uidState.pkgOps.keyAt(pkgi))); - } - } - } - } - } - } - } - uidState.state = uidState.pendingState; - uidState.capability = uidState.pendingCapability; - uidState.appWidgetVisible = uidState.pendingAppWidgetVisible; - uidState.pendingStateCommitTime = 0; + return uidState; } private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { synchronized (this) { - for (int i = uidPackageNames.size() - 1; i >= 0; i--) { - final int uid = uidPackageNames.keyAt(i); - final UidState uidState = getUidStateLocked(uid, true); - if (uidState != null && (uidState.pendingAppWidgetVisible != visible)) { - uidState.pendingAppWidgetVisible = visible; - if (uidState.pendingAppWidgetVisible != uidState.appWidgetVisible) { - commitUidPendingStateLocked(uidState); - } - } - } + getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); } } @@ -5885,6 +5734,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch boolean includeDiscreteOps = false; int nDiscreteOps = 10; @HistoricalOpsRequestFilter int dumpFilter = 0; + boolean dumpAll = false; if (args != null) { for (int i = 0; i < args.length; i++) { @@ -5894,6 +5744,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return; } else if ("-a".equals(arg)) { // dump all data + dumpAll = true; } else if ("--op".equals(arg)) { i++; if (i >= args.length) { @@ -6207,31 +6058,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":"); - pw.print(" state="); - pw.println(AppOpsManager.getUidStateName(uidState.state)); - if (uidState.state != uidState.pendingState) { - pw.print(" pendingState="); - pw.println(AppOpsManager.getUidStateName(uidState.pendingState)); - } - pw.print(" capability="); - ActivityManager.printCapabilitiesFull(pw, uidState.capability); - pw.println(); - if (uidState.capability != uidState.pendingCapability) { - pw.print(" pendingCapability="); - ActivityManager.printCapabilitiesFull(pw, uidState.pendingCapability); - pw.println(); - } - pw.print(" appWidgetVisible="); - pw.println(uidState.appWidgetVisible); - if (uidState.appWidgetVisible != uidState.pendingAppWidgetVisible) { - pw.print(" pendingAppWidgetVisible="); - pw.println(uidState.pendingAppWidgetVisible); - } - if (uidState.pendingStateCommitTime != 0) { - pw.print(" pendingStateCommitTime="); - TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowElapsed, pw); - pw.println(); - } + uidState.dump(pw, nowElapsed); if (uidState.foregroundOps != null && (dumpMode < 0 || dumpMode == AppOpsManager.MODE_FOREGROUND)) { pw.println(" foregroundOps:"); @@ -6450,6 +6277,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag, dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps); } + + if (dumpAll) { + pw.println(); + pw.println("Uid State Changes Event Log:"); + if (mUidStateTracker != null) { + mUidStateTracker.dumpEvents(pw); + } + } } @Override diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java new file mode 100644 index 000000000000..3da121bc73fa --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; +import static android.app.AppOpsManager.UID_STATE_BACKGROUND; +import static android.app.AppOpsManager.UID_STATE_CACHED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.UID_STATE_PERSISTENT; +import static android.app.AppOpsManager.UID_STATE_TOP; + +import android.os.Handler; +import android.util.SparseArray; + +import java.io.PrintWriter; + +interface AppOpsUidStateTracker { + + // Map from process states to the uid states we track. + static int processStateToUidState(int procState) { + if (procState == PROCESS_STATE_UNKNOWN) { + return UID_STATE_CACHED; + } + + if (procState <= PROCESS_STATE_PERSISTENT_UI) { + return UID_STATE_PERSISTENT; + } + + if (procState <= PROCESS_STATE_TOP) { + return UID_STATE_TOP; + } + + if (procState <= PROCESS_STATE_BOUND_TOP) { + return UID_STATE_FOREGROUND; + } + + if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) { + return UID_STATE_FOREGROUND_SERVICE; + } + + if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + return UID_STATE_FOREGROUND; + } + + if (procState <= PROCESS_STATE_RECEIVER) { + return UID_STATE_BACKGROUND; + } + + return UID_STATE_CACHED; + } + + /* + * begin data pushed from appopsservice + */ + + void updateUidProcState(int uid, int procState, int capability); + + void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible); + + /* + * end data pushed from appopsservice + */ + + /** + * Gets the {@link android.app.AppOpsManager.UidState} that the uid current is in. + */ + int getUidState(int uid); + + /** + * Given a uid, code, and mode, resolve any foregroundness to MODE_IGNORED or MODE_ALLOWED + */ + int evalMode(int uid, int code, int mode); + + /** + * Listen to changes in {@link android.app.AppOpsManager.UidState} + */ + void addUidStateChangedCallback(Handler handler, + UidStateChangedCallback callback); + + /** + * Remove a {@link UidStateChangedCallback} + */ + void removeUidStateChangedCallback(UidStateChangedCallback callback); + + interface UidStateChangedCallback { + /** + * Invoked when a UID's {@link android.app.AppOpsManager.UidState} changes. + * @param uid The uid that changed. + * @param uidState The state that was changed to. + * @param foregroundModeMayChange True if there may be a op in MODE_FOREGROUND whose + * evaluated result may have changed. + */ + void onUidStateChanged(int uid, int uidState, boolean foregroundModeMayChange); + } + + void dumpUidState(PrintWriter pw, int uid, long nowElapsed); + + void dumpEvents(PrintWriter pw); +} diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java new file mode 100644 index 000000000000..ca5bfb36723c --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManager.ProcessCapability; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.UID_STATE_CACHED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; +import static android.app.AppOpsManager.UID_STATE_TOP; + +import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.os.Handler; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.SparseLongArray; +import android.util.TimeUtils; + +import com.android.internal.os.Clock; +import com.android.internal.util.function.pooled.PooledLambda; + +import java.io.PrintWriter; +import java.util.concurrent.CountDownLatch; + +class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { + + private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName(); + + private final Handler mHandler; + private final Clock mClock; + private ActivityManagerInternal mActivityManagerInternal; + private AppOpsService.Constants mConstants; + + private SparseIntArray mUidStates = new SparseIntArray(); + private SparseIntArray mPendingUidStates = new SparseIntArray(); + private SparseIntArray mCapability = new SparseIntArray(); + private SparseIntArray mPendingCapability = new SparseIntArray(); + private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray(); + private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray(); + private SparseLongArray mPendingCommitTime = new SparseLongArray(); + private SparseBooleanArray mPendingGone = new SparseBooleanArray(); + + private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>(); + + private final EventLog mEventLog; + + AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, + Handler handler, Clock clock, AppOpsService.Constants constants) { + mActivityManagerInternal = activityManagerInternal; + mHandler = handler; + mClock = clock; + mConstants = constants; + + mEventLog = new EventLog(handler); + } + + @Override + public int getUidState(int uid) { + return getUidStateLocked(uid); + } + + private int getUidStateLocked(int uid) { + updateUidPendingStateIfNeeded(uid); + return mUidStates.get(uid, UID_STATE_CACHED); + } + + @Override + public int evalMode(int uid, int code, int mode) { + if (mode != AppOpsManager.MODE_FOREGROUND) { + return mode; + } + + int uidStateValue; + int capability; + boolean visibleAppWidget; + boolean pendingTop; + boolean tempAllowlist; + uidStateValue = getUidState(uid); + capability = getUidCapability(uid); + visibleAppWidget = getUidVisibleAppWidget(uid); + pendingTop = mActivityManagerInternal.isPendingTopUid(uid); + tempAllowlist = mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid); + + int result = evalMode(uidStateValue, code, mode, capability, visibleAppWidget, pendingTop, + tempAllowlist); + mEventLog.logEvalForegroundMode(uid, uidStateValue, capability, code, result); + return result; + } + + private static int evalMode(int uidState, int code, int mode, int capability, + boolean appWidgetVisible, boolean pendingTop, boolean tempAllowlist) { + if (mode != AppOpsManager.MODE_FOREGROUND) { + return mode; + } + + if (appWidgetVisible || pendingTop || tempAllowlist) { + return MODE_ALLOWED; + } + + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + case AppOpsManager.OP_COARSE_LOCATION: + case AppOpsManager.OP_MONITOR_LOCATION: + case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) == 0) { + return MODE_IGNORED; + } else { + return MODE_ALLOWED; + } + case OP_CAMERA: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) == 0) { + return MODE_IGNORED; + } else { + return MODE_ALLOWED; + } + case OP_RECORD_AUDIO: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) { + return MODE_IGNORED; + } else { + return MODE_ALLOWED; + } + } + + if (uidState > AppOpsManager.resolveFirstUnrestrictedUidState(code)) { + return MODE_IGNORED; + } + + return MODE_ALLOWED; + } + + @Override + public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) { + if (mUidStateChangedCallbacks.containsKey(callback)) { + throw new IllegalStateException("Callback is already registered."); + } + mUidStateChangedCallbacks.put(callback, handler); + } + + @Override + public void removeUidStateChangedCallback(UidStateChangedCallback callback) { + if (!mUidStateChangedCallbacks.containsKey(callback)) { + throw new IllegalStateException("Callback is not registered."); + } + mUidStateChangedCallbacks.remove(callback); + } + + @Override + public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { + int numUids = uidPackageNames.size(); + for (int i = 0; i < numUids; i++) { + int uid = uidPackageNames.keyAt(i); + mPendingVisibleAppWidget.put(uid, visible); + + commitUidPendingState(uid); + } + } + + @Override + public void updateUidProcState(int uid, int procState, int capability) { + mEventLog.logUpdateUidProcState(uid, procState, capability); + + int uidState = processStateToUidState(procState); + + int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE); + int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); + long pendingStateCommitTime = mPendingCommitTime.get(uid, 0); + if (uidState != prevUidState || capability != prevCapability) { + mPendingUidStates.put(uid, uidState); + mPendingCapability.put(uid, capability); + + if (procState == PROCESS_STATE_NONEXISTENT) { + mPendingGone.put(uid, true); + commitUidPendingState(uid); + } else if (uidState < prevUidState + || (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + && prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) { + // We are moving to a more important state, or the new state may be in the + // foreground and the old state is in the background, then always do it + // immediately. + commitUidPendingState(uid); + } else if (uidState == prevUidState && capability != prevCapability) { + // No change on process state, but process capability has changed. + commitUidPendingState(uid); + } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED) { + // We are moving to a less important state, but it doesn't cross the restriction + // threshold. + commitUidPendingState(uid); + } else if (pendingStateCommitTime == 0) { + // We are moving to a less important state for the first time, + // delay the application for a bit. + final long settleTime; + if (prevUidState <= UID_STATE_TOP) { + settleTime = mConstants.TOP_STATE_SETTLE_TIME; + } else if (prevUidState <= UID_STATE_FOREGROUND_SERVICE) { + settleTime = mConstants.FG_SERVICE_STATE_SETTLE_TIME; + } else { + settleTime = mConstants.BG_STATE_SETTLE_TIME; + } + final long commitTime = mClock.elapsedRealtime() + settleTime; + mPendingCommitTime.put(uid, commitTime); + + mHandler.sendMessageDelayed(PooledLambda.obtainMessage( + AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this, + uid), settleTime + 1); + } + } + } + + @Override + public void dumpUidState(PrintWriter pw, int uid, long nowElapsed) { + int state = mUidStates.get(uid, UID_STATE_CACHED); + // if no pendingState set to state to suppress output + int pendingState = mPendingUidStates.get(uid, state); + pw.print(" state="); + pw.println(AppOpsManager.getUidStateName(state)); + if (state != pendingState) { + pw.print(" pendingState="); + pw.println(AppOpsManager.getUidStateName(pendingState)); + } + int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); + // if no pendingCapability set to capability to suppress output + int pendingCapability = mPendingCapability.get(uid, capability); + pw.print(" capability="); + ActivityManager.printCapabilitiesFull(pw, capability); + pw.println(); + if (capability != pendingCapability) { + pw.print(" pendingCapability="); + ActivityManager.printCapabilitiesFull(pw, pendingCapability); + pw.println(); + } + boolean appWidgetVisible = mVisibleAppWidget.get(uid, false); + // if no pendingAppWidgetVisible set to appWidgetVisible to suppress output + boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible); + pw.print(" appWidgetVisible="); + pw.println(appWidgetVisible); + if (appWidgetVisible != pendingAppWidgetVisible) { + pw.print(" pendingAppWidgetVisible="); + pw.println(pendingAppWidgetVisible); + } + long pendingStateCommitTime = mPendingCommitTime.get(uid, 0); + if (pendingStateCommitTime != 0) { + pw.print(" pendingStateCommitTime="); + TimeUtils.formatDuration(pendingStateCommitTime, nowElapsed, pw); + pw.println(); + } + } + + @Override + public void dumpEvents(PrintWriter pw) { + mEventLog.dumpEvents(pw); + } + + private void updateUidPendingStateIfNeeded(int uid) { + updateUidPendingStateIfNeededLocked(uid); + } + + private void updateUidPendingStateIfNeededLocked(int uid) { + long pendingCommitTime = mPendingCommitTime.get(uid, 0); + if (pendingCommitTime != 0) { + long currentTime = mClock.elapsedRealtime(); + if (currentTime < mPendingCommitTime.get(uid)) { + return; + } + commitUidPendingState(uid); + } + } + + private void commitUidPendingState(int uid) { + int pendingUidState = mPendingUidStates.get(uid, UID_STATE_CACHED); + int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE); + boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, false); + + int uidState = mUidStates.get(uid, UID_STATE_CACHED); + int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); + boolean visibleAppWidget = mVisibleAppWidget.get(uid, false); + + if (uidState != pendingUidState + || capability != pendingCapability + || visibleAppWidget != pendingVisibleAppWidget) { + boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + || capability != pendingCapability + || visibleAppWidget != pendingVisibleAppWidget; + + if (foregroundChange) { + // To save on memory usage, log only interesting changes. + mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability, + pendingVisibleAppWidget); + } + + for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { + UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i); + Handler h = mUidStateChangedCallbacks.valueAt(i); + + h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged, + cb, uid, pendingUidState, foregroundChange)); + } + } + + if (mPendingGone.get(uid, false)) { + mUidStates.delete(uid); + mCapability.delete(uid); + mVisibleAppWidget.delete(uid); + mPendingGone.delete(uid); + } else { + mUidStates.put(uid, pendingUidState); + mCapability.put(uid, pendingCapability); + mVisibleAppWidget.put(uid, pendingVisibleAppWidget); + } + + mPendingUidStates.delete(uid); + mPendingCapability.delete(uid); + mPendingVisibleAppWidget.delete(uid); + mPendingCommitTime.delete(uid); + } + + private @ProcessCapability int getUidCapability(int uid) { + return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE); + } + + private boolean getUidVisibleAppWidget(int uid) { + return mVisibleAppWidget.get(uid, false); + } + + private static class EventLog { + + // These seems a bit too verbose and not as useful, turning off for now. + // DCE should be able to remove most associated code. + // Memory usage: 16 * size bytes + private static final int UPDATE_UID_PROC_STATE_LOG_MAX_SIZE = 0; + // Memory usage: 20 * size bytes + private static final int COMMIT_UID_STATE_LOG_MAX_SIZE = 200; + // Memory usage: 24 * size bytes + private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200; + + private final Handler mHandler; + + private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3]; + private long[] mUpdateUidProcStateLogTimestamps = + new long[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE]; + private int mUpdateUidProcStateLogSize = 0; + private int mUpdateUidProcStateLogHead = 0; + + private int[][] mCommitUidStateLog = new int[COMMIT_UID_STATE_LOG_MAX_SIZE][4]; + private long[] mCommitUidStateLogTimestamps = new long[COMMIT_UID_STATE_LOG_MAX_SIZE]; + private int mCommitUidStateLogSize = 0; + private int mCommitUidStateLogHead = 0; + + private int[][] mEvalForegroundModeLog = new int[EVAL_FOREGROUND_MODE_MAX_SIZE][5]; + private long[] mEvalForegroundModeLogTimestamps = new long[EVAL_FOREGROUND_MODE_MAX_SIZE]; + private int mEvalForegroundModeLogSize = 0; + private int mEvalForegroundModeLogHead = 0; + + EventLog(Handler handler) { + mHandler = handler; + } + + void logUpdateUidProcState(int uid, int procState, int capability) { + if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logUpdateUidProcStateAsync, + this, System.currentTimeMillis(), uid, procState, capability)); + } + + void logUpdateUidProcStateAsync(long timestamp, int uid, int procState, int capability) { + int idx = (mUpdateUidProcStateLogHead + mUpdateUidProcStateLogSize) + % UPDATE_UID_PROC_STATE_LOG_MAX_SIZE; + if (mUpdateUidProcStateLogSize == UPDATE_UID_PROC_STATE_LOG_MAX_SIZE) { + mUpdateUidProcStateLogHead = + (mUpdateUidProcStateLogHead + 1) % UPDATE_UID_PROC_STATE_LOG_MAX_SIZE; + } else { + mUpdateUidProcStateLogSize++; + } + + mUpdateUidProcStateLog[idx][0] = uid; + mUpdateUidProcStateLog[idx][1] = procState; + mUpdateUidProcStateLog[idx][2] = capability; + mUpdateUidProcStateLogTimestamps[idx] = timestamp; + } + + void logCommitUidState(int uid, int uidState, int capability, boolean visible) { + if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logCommitUidStateAsync, + this, System.currentTimeMillis(), uid, uidState, capability, visible)); + } + + void logCommitUidStateAsync(long timestamp, int uid, int uidState, int capability, + boolean visible) { + int idx = (mCommitUidStateLogHead + mCommitUidStateLogSize) + % COMMIT_UID_STATE_LOG_MAX_SIZE; + if (mCommitUidStateLogSize == COMMIT_UID_STATE_LOG_MAX_SIZE) { + mCommitUidStateLogHead = + (mCommitUidStateLogHead + 1) % COMMIT_UID_STATE_LOG_MAX_SIZE; + } else { + mCommitUidStateLogSize++; + } + + mCommitUidStateLog[idx][0] = uid; + mCommitUidStateLog[idx][1] = uidState; + mCommitUidStateLog[idx][2] = capability; + mCommitUidStateLog[idx][3] = visible ? 1 : 0; + mCommitUidStateLogTimestamps[idx] = timestamp; + } + + void logEvalForegroundMode(int uid, int uidState, int capability, int code, int result) { + if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logEvalForegroundModeAsync, + this, System.currentTimeMillis(), uid, uidState, capability, code, result)); + } + + void logEvalForegroundModeAsync(long timestamp, int uid, int uidState, int capability, + int code, int result) { + int idx = (mEvalForegroundModeLogHead + mEvalForegroundModeLogSize) + % EVAL_FOREGROUND_MODE_MAX_SIZE; + if (mEvalForegroundModeLogSize == EVAL_FOREGROUND_MODE_MAX_SIZE) { + mEvalForegroundModeLogHead = + (mEvalForegroundModeLogHead + 1) % EVAL_FOREGROUND_MODE_MAX_SIZE; + } else { + mEvalForegroundModeLogSize++; + } + + mEvalForegroundModeLog[idx][0] = uid; + mEvalForegroundModeLog[idx][1] = uidState; + mEvalForegroundModeLog[idx][2] = capability; + mEvalForegroundModeLog[idx][3] = code; + mEvalForegroundModeLog[idx][4] = result; + mEvalForegroundModeLogTimestamps[idx] = timestamp; + } + + void dumpEvents(PrintWriter pw) { + if (Thread.currentThread() != mHandler.getLooper().getThread()) { + // All operations are done on the handler's thread + CountDownLatch latch = new CountDownLatch(1); + mHandler.post(() -> { + dumpEvents(pw); + latch.countDown(); + }); + + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return; + } + + int updateIdx = 0; + int commitIdx = 0; + int evalIdx = 0; + + while (updateIdx < mUpdateUidProcStateLogSize + || commitIdx < mCommitUidStateLogSize + || evalIdx < mEvalForegroundModeLogSize) { + int updatePtr = 0; + int commitPtr = 0; + int evalPtr = 0; + if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE != 0) { + updatePtr = (mUpdateUidProcStateLogHead + updateIdx) + % UPDATE_UID_PROC_STATE_LOG_MAX_SIZE; + } + if (COMMIT_UID_STATE_LOG_MAX_SIZE != 0) { + commitPtr = (mCommitUidStateLogHead + commitIdx) + % COMMIT_UID_STATE_LOG_MAX_SIZE; + } + if (EVAL_FOREGROUND_MODE_MAX_SIZE != 0) { + evalPtr = (mEvalForegroundModeLogHead + evalIdx) + % EVAL_FOREGROUND_MODE_MAX_SIZE; + } + + long aTimestamp = updateIdx < mUpdateUidProcStateLogSize + ? mUpdateUidProcStateLogTimestamps[updatePtr] : Long.MAX_VALUE; + long bTimestamp = commitIdx < mCommitUidStateLogSize + ? mCommitUidStateLogTimestamps[commitPtr] : Long.MAX_VALUE; + long cTimestamp = evalIdx < mEvalForegroundModeLogSize + ? mEvalForegroundModeLogTimestamps[evalPtr] : Long.MAX_VALUE; + + if (aTimestamp <= bTimestamp && aTimestamp <= cTimestamp) { + dumpUpdateUidProcState(pw, updatePtr); + updateIdx++; + } else if (bTimestamp <= cTimestamp) { + dumpCommitUidState(pw, commitPtr); + commitIdx++; + } else { + dumpEvalForegroundMode(pw, evalPtr); + evalIdx++; + } + } + } + + void dumpUpdateUidProcState(PrintWriter pw, int idx) { + long timestamp = mUpdateUidProcStateLogTimestamps[idx]; + int uid = mUpdateUidProcStateLog[idx][0]; + int procState = mUpdateUidProcStateLog[idx][1]; + int capability = mUpdateUidProcStateLog[idx][2]; + + TimeUtils.dumpTime(pw, timestamp); + + pw.print(" UPDATE_UID_PROC_STATE"); + + pw.print(" uid="); + pw.print(uid); + + pw.print(" procState="); + pw.print(String.format("%-30s", ActivityManager.procStateToString(procState))); + + pw.print(" capability="); + pw.print(ActivityManager.getCapabilitiesSummary(capability)); + + pw.println(); + } + + void dumpCommitUidState(PrintWriter pw, int idx) { + long timestamp = mCommitUidStateLogTimestamps[idx]; + int uid = mCommitUidStateLog[idx][0]; + int uidState = mCommitUidStateLog[idx][1]; + int capability = mCommitUidStateLog[idx][2]; + boolean visibleAppWidget = mCommitUidStateLog[idx][3] != 0; + + TimeUtils.dumpTime(pw, timestamp); + + pw.print(" COMMIT_UID_STATE "); + + pw.print(" uid="); + pw.print(uid); + + pw.print(" uidState="); + pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState))); + + pw.print(" capability="); + pw.print(ActivityManager.getCapabilitiesSummary(capability)); + + pw.print(" visibleAppWidget="); + pw.print(visibleAppWidget); + + pw.println(); + } + + void dumpEvalForegroundMode(PrintWriter pw, int idx) { + long timestamp = mEvalForegroundModeLogTimestamps[idx]; + int uid = mEvalForegroundModeLog[idx][0]; + int uidState = mEvalForegroundModeLog[idx][1]; + int capability = mEvalForegroundModeLog[idx][2]; + int code = mEvalForegroundModeLog[idx][3]; + int result = mEvalForegroundModeLog[idx][4]; + + TimeUtils.dumpTime(pw, timestamp); + + pw.print(" EVAL_FOREGROUND_MODE "); + + pw.print(" uid="); + pw.print(uid); + + pw.print(" uidState="); + pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState))); + + pw.print(" capability="); + pw.print(ActivityManager.getCapabilitiesSummary(capability)); + + pw.print(" code="); + pw.print(String.format("%-20s", AppOpsManager.opToName(code))); + + pw.print(" result="); + pw.print(AppOpsManager.modeToName(result)); + + pw.println(); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index e97d9c22620e..736914ace215 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -17,9 +17,12 @@ package com.android.server.audio; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.compat.CompatChanges; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -118,8 +121,39 @@ import java.util.concurrent.atomic.AtomicBoolean; // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055 /*package*/ final Object mSetModeLock = new Object(); - /** PID of current audio mode owner communicated by AudioService */ - private int mModeOwnerPid = 0; + /** AudioModeInfo contains information on current audio mode owner + * communicated by AudioService */ + /* package */ static final class AudioModeInfo { + /** Current audio mode */ + final int mMode; + /** PID of current audio mode owner */ + final int mPid; + /** UID of current audio mode owner */ + final int mUid; + + AudioModeInfo(int mode, int pid, int uid) { + mMode = mode; + mPid = pid; + mUid = uid; + } + + @Override + public String toString() { + return "AudioModeInfo: mMode=" + AudioSystem.modeToString(mMode) + + ", mPid=" + mPid + + ", mUid=" + mUid; + } + }; + + private AudioModeInfo mAudioModeOwner = new AudioModeInfo(AudioSystem.MODE_NORMAL, 0, 0); + + /** + * Indicates that default communication device is chosen by routing rules in audio policy + * manager and not forced by AudioDeviceBroker. + */ + @ChangeId + @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2) + public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L; //------------------------------------------------------------------- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { @@ -187,7 +221,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /*package*/ void onSystemReady() { synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - mModeOwnerPid = mAudioService.getModeOwnerPid(); + mAudioModeOwner = mAudioService.getAudioModeOwner(); mBtHelper.onSystemReady(); } } @@ -368,11 +402,11 @@ import java.util.concurrent.atomic.AtomicBoolean; @GuardedBy("mDeviceStateLock") private CommunicationRouteClient topCommunicationRouteClient() { for (CommunicationRouteClient crc : mCommunicationRouteClients) { - if (crc.getPid() == mModeOwnerPid) { + if (crc.getPid() == mAudioModeOwner.mPid) { return crc; } } - if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) { + if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) { return mCommunicationRouteClients.get(0); } return null; @@ -390,7 +424,7 @@ import java.util.concurrent.atomic.AtomicBoolean; AudioDeviceAttributes device = crc != null ? crc.getDevice() : null; if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "requestedCommunicationDevice, device: " - + device + " mode owner pid: " + mModeOwnerPid); + + device + "mAudioModeOwner: " + mAudioModeOwner.toString()); } return device; } @@ -774,8 +808,9 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info); } - /*package*/ void postSetModeOwnerPid(int pid, int mode) { - sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode); + /*package*/ void postSetModeOwner(int mode, int pid, int uid) { + sendLMsgNoDelay(MSG_I_SET_MODE_OWNER, SENDMSG_REPLACE, + new AudioModeInfo(mode, pid, uid)); } /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { @@ -1162,7 +1197,7 @@ import java.util.concurrent.atomic.AtomicBoolean; pw.println(prefix + "mAccessibilityStrategyId: " + mAccessibilityStrategyId); - pw.println("\n" + prefix + "mModeOwnerPid: " + mModeOwnerPid); + pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner); mBtHelper.dump(pw, prefix); } @@ -1288,12 +1323,19 @@ import java.util.concurrent.atomic.AtomicBoolean; } break; case MSG_L_SET_BT_ACTIVE_DEVICE: - synchronized (mDeviceStateLock) { - BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; - mDeviceInventory.onSetBtActiveDevice(btInfo, - (btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput) - ? mAudioService.getBluetoothContextualVolumeStream() - : AudioSystem.STREAM_DEFAULT); + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; + mDeviceInventory.onSetBtActiveDevice(btInfo, + (btInfo.mProfile + != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput) + ? mAudioService.getBluetoothContextualVolumeStream() + : AudioSystem.STREAM_DEFAULT); + if (btInfo.mProfile == BluetoothProfile.LE_AUDIO + || btInfo.mProfile == BluetoothProfile.HEARING_AID) { + onUpdateCommunicationRouteClient("setBluetoothActiveDevice"); + } + } } break; case MSG_BT_HEADSET_CNCT_FAILED: @@ -1338,11 +1380,11 @@ import java.util.concurrent.atomic.AtomicBoolean; mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); } break; - case MSG_I_SET_MODE_OWNER_PID: + case MSG_I_SET_MODE_OWNER: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - mModeOwnerPid = msg.arg1; - if (msg.arg2 != AudioSystem.MODE_RINGTONE) { + mAudioModeOwner = (AudioModeInfo) msg.obj; + if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) { onUpdateCommunicationRouteClient("setNewModeOwner"); } } @@ -1504,7 +1546,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_REPORT_NEW_ROUTES = 13; private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; - private static final int MSG_I_SET_MODE_OWNER_PID = 16; + private static final int MSG_I_SET_MODE_OWNER = 16; private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22; private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23; @@ -1826,8 +1868,16 @@ import java.util.concurrent.atomic.AtomicBoolean; AudioSystem.setParameters("BT_SCO=on"); } if (preferredCommunicationDevice == null) { - removePreferredDevicesForStrategySync(mCommunicationStrategyId); - removePreferredDevicesForStrategySync(mAccessibilityStrategyId); + AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice(); + if (defaultDevice != null) { + setPreferredDevicesForStrategySync( + mCommunicationStrategyId, Arrays.asList(defaultDevice)); + setPreferredDevicesForStrategySync( + mAccessibilityStrategyId, Arrays.asList(defaultDevice)); + } else { + removePreferredDevicesForStrategySync(mCommunicationStrategyId); + removePreferredDevicesForStrategySync(mAccessibilityStrategyId); + } } else { setPreferredDevicesForStrategySync( mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice)); @@ -1856,26 +1906,24 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + // @GuardedBy("mSetModeLock") + @GuardedBy("mDeviceStateLock") private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) { - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - boolean wasSpeakerphoneActive = isSpeakerphoneActive(); - mPreferredCommunicationDevice = device; - updateActiveCommunicationDevice(); - if (wasSpeakerphoneActive != isSpeakerphoneActive()) { - try { - mContext.sendBroadcastAsUser( - new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - UserHandle.ALL); - } catch (Exception e) { - Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e); - } - } - mAudioService.postUpdateRingerModeServiceInt(); - dispatchCommunicationDevice(); + boolean wasSpeakerphoneActive = isSpeakerphoneActive(); + mPreferredCommunicationDevice = device; + updateActiveCommunicationDevice(); + if (wasSpeakerphoneActive != isSpeakerphoneActive()) { + try { + mContext.sendBroadcastAsUser( + new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), + UserHandle.ALL); + } catch (Exception e) { + Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e); } } + mAudioService.postUpdateRingerModeServiceInt(); + dispatchCommunicationDevice(); } private CommunicationRouteClient removeCommunicationRouteClient( @@ -1915,6 +1963,32 @@ import java.util.concurrent.atomic.AtomicBoolean; return null; } + @GuardedBy("mDeviceStateLock") + private boolean communnicationDeviceCompatOn() { + return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION + && !(CompatChanges.isChangeEnabled( + USE_SET_COMMUNICATION_DEVICE, mAudioModeOwner.mUid) + || mAudioModeOwner.mUid == android.os.Process.SYSTEM_UID); + } + + @GuardedBy("mDeviceStateLock") + AudioDeviceAttributes getDefaultCommunicationDevice() { + // For system server (Telecom) and APKs targeting S and above, we let the audio + // policy routing rules select the default communication device. + // For older APKs, we force Hearing Aid or LE Audio headset when connected as + // those APKs cannot select a LE Audio or Hearing Aid device explicitly. + AudioDeviceAttributes device = null; + if (communnicationDeviceCompatOn()) { + // If both LE and Hearing Aid are active (thie should not happen), + // priority to Hearing Aid. + device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_HEARING_AID); + if (device == null) { + device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + } + return device; + } + @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { synchronized (mDeviceStateLock) { return mDeviceInventory.getDeviceSensorUuid(device); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index c1f496905dba..e90bfe85fc89 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -294,6 +294,7 @@ public class AudioDeviceInventory { } } + // @GuardedBy("AudioDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock") void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) { if (AudioService.DEBUG_DEVICES) { @@ -1526,6 +1527,19 @@ public class AudioDeviceInventory { return di.mSensorUuid; } } + + /* package */ AudioDeviceAttributes getDeviceOfType(int type) { + synchronized (mDevicesLock) { + for (DeviceInfo di : mConnectedDevices.values()) { + if (di.mDeviceType == type) { + return new AudioDeviceAttributes( + di.mDeviceType, di.mDeviceAddress, di.mDeviceName); + } + } + } + return null; + } + //---------------------------------------------------------- // For tests only diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 31f86599b865..c8aecafb0b7f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5230,16 +5230,17 @@ public class AudioService extends IAudioService.Stub } /** - * Return the pid of the current audio mode owner + * Return information on the current audio mode owner * @return 0 if nobody owns the mode */ @GuardedBy("mDeviceBroker.mSetModeLock") - /*package*/ int getModeOwnerPid() { + /*package*/ AudioDeviceBroker.AudioModeInfo getAudioModeOwner() { SetModeDeathHandler hdlr = getAudioModeOwnerHandler(); if (hdlr != null) { - return hdlr.getPid(); + return new AudioDeviceBroker.AudioModeInfo( + hdlr.getMode(), hdlr.getPid(), hdlr.getUid()); } - return 0; + return new AudioDeviceBroker.AudioModeInfo(AudioSystem.MODE_NORMAL, 0 , 0); } /** @@ -5425,7 +5426,7 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO // connections not started by the application changing the mode when pid changes - mDeviceBroker.postSetModeOwnerPid(pid, mode); + mDeviceBroker.postSetModeOwner(mode, pid, uid); } else { Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode); } @@ -5753,7 +5754,10 @@ public class AudioService extends IAudioService.Stub } return deviceIds.stream().mapToInt(Integer::intValue).toArray(); } - /** @see AudioManager#setCommunicationDevice(int) */ + /** + * @see AudioManager#setCommunicationDevice(int) + * @see AudioManager#clearCommunicationDevice() + */ public boolean setCommunicationDevice(IBinder cb, int portId) { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); @@ -5768,7 +5772,8 @@ public class AudioService extends IAudioService.Stub throw new IllegalArgumentException("invalid device type " + device.getType()); } } - final String eventSource = new StringBuilder("setCommunicationDevice(") + final String eventSource = new StringBuilder() + .append(device == null ? "clearCommunicationDevice(" : "setCommunicationDevice(") .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 454894e2f26e..e5d40d93909f 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -1150,18 +1150,29 @@ public final class PlaybackActivityMonitor } return builder.toString(); case AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED: - builder.append(" muteFromMasterMute:").append( - (mEventValue & PLAYER_MUTE_MASTER) != 0); - builder.append(" muteFromStreamVolume:").append( - (mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0); - builder.append(" muteFromStreamMute:").append( - (mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0); - builder.append(" muteFromPlaybackRestricted:").append( - (mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0); - builder.append(" muteFromClientVolume:").append( - (mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0); - builder.append(" muteFromVolumeShaper:").append( - (mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0); + builder.append(" source:"); + if (mEventValue <= 0) { + builder.append("none "); + } else { + if ((mEventValue & PLAYER_MUTE_MASTER) != 0) { + builder.append("masterMute "); + } + if ((mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0) { + builder.append("streamVolume "); + } + if ((mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0) { + builder.append("streamMute "); + } + if ((mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) { + builder.append("playbackRestricted "); + } + if ((mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0) { + builder.append("clientVolume "); + } + if ((mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0) { + builder.append("volumeShaper "); + } + } return builder.toString(); default: return builder.toString(); diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java index 46d863d7aaec..2e1a363bcc68 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java @@ -59,7 +59,7 @@ public class ClientMonitorCallbackConverter { // The following apply to all clients - void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException { + public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException { if (mSensorReceiver != null) { mSensorReceiver.onAcquired(sensorId, acquiredInfo, vendorCode); } else if (mFaceServiceReceiver != null) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index e0393b54d2c5..612d90670888 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -50,12 +50,14 @@ import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; +import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; import java.util.function.Supplier; -class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps { +class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, + PowerPressHandler { private static final String TAG = "FingerprintEnrollClient"; @@ -266,4 +268,10 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps Slog.e(TAG, "Unable to send UI ready", e); } } + + @Override + public void onPowerPressed() { + onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED, + 0 /* vendorCode */); + } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 431be88bd892..676dc196f20c 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -99,6 +99,7 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.CancellationSignal; import android.os.FileUtils; +import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Looper; @@ -2784,26 +2785,18 @@ public class Vpn { // When restricted to test networks, select any network with TRANSPORT_TEST. Since the // creator of the profile and the test network creator both have MANAGE_TEST_NETWORKS, // this is considered safe. - final NetworkRequest req; if (mProfile.isRestrictedToTestNetworks()) { - req = new NetworkRequest.Builder() + final NetworkRequest req = new NetworkRequest.Builder() .clearCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_TEST) .addCapability(NET_CAPABILITY_NOT_VPN) .build(); + mConnectivityManager.requestNetwork(req, mNetworkCallback); } else { - // Basically, the request here is referring to the default request which is defined - // in ConnectivityService. Ideally, ConnectivityManager should provide an new API - // which can provide the status of physical network even though there is a virtual - // network. b/147280869 is used for tracking the new API. - // TODO: Use the new API to register default physical network. - req = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build(); + mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback, + new Handler(mLooper)); } - - mConnectivityManager.requestNetwork(req, mNetworkCallback); } private boolean isActiveNetwork(@Nullable Network network) { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 6145a91cf7cd..b3aee22547e5 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -131,6 +131,8 @@ class AutomaticBrightnessController { // Configuration object for determining thresholds to change brightness dynamically private final HysteresisLevels mAmbientBrightnessThresholds; private final HysteresisLevels mScreenBrightnessThresholds; + private final HysteresisLevels mAmbientBrightnessThresholdsIdle; + private final HysteresisLevels mScreenBrightnessThresholdsIdle; private boolean mLoggingEnabled; @@ -242,7 +244,9 @@ class AutomaticBrightnessController { float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels screenBrightnessThresholds, Context context, + HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, + HysteresisLevels screenBrightnessThresholdsIdle, Context context, HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong) { @@ -251,7 +255,8 @@ class AutomaticBrightnessController { lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, - ambientBrightnessThresholds, screenBrightnessThresholds, context, + ambientBrightnessThresholds, screenBrightnessThresholds, + ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong ); @@ -265,7 +270,9 @@ class AutomaticBrightnessController { float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels screenBrightnessThresholds, Context context, + HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, + HysteresisLevels screenBrightnessThresholdsIdle, Context context, HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong) { @@ -289,7 +296,9 @@ class AutomaticBrightnessController { mAmbientLightHorizonShort = ambientLightHorizonShort; mWeightingIntercept = ambientLightHorizonLong; mAmbientBrightnessThresholds = ambientBrightnessThresholds; + mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle; mScreenBrightnessThresholds = screenBrightnessThresholds; + mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle; mShortTermModelValid = true; mShortTermModelAnchor = -1; mHandler = new AutomaticBrightnessHandler(looper); @@ -586,6 +595,8 @@ class AutomaticBrightnessController { pw.println(); mAmbientBrightnessThresholds.dump(pw); mScreenBrightnessThresholds.dump(pw); + mScreenBrightnessThresholdsIdle.dump(pw); + mScreenBrightnessThresholdsIdle.dump(pw); } private String configStateToString(int state) { @@ -680,8 +691,17 @@ class AutomaticBrightnessController { lux = 0; } mAmbientLux = lux; - mAmbientBrighteningThreshold = mAmbientBrightnessThresholds.getBrighteningThreshold(lux); - mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux); + if (isInIdleMode()) { + mAmbientBrighteningThreshold = + mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux); + mAmbientDarkeningThreshold = + mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux); + } else { + mAmbientBrighteningThreshold = + mAmbientBrightnessThresholds.getBrighteningThreshold(lux); + mAmbientDarkeningThreshold = + mAmbientBrightnessThresholds.getDarkeningThreshold(lux); + } mHbmController.onAmbientLuxChange(mAmbientLux); // If the short term model was invalidated and the change is drastic enough, reset it. @@ -903,10 +923,20 @@ class AutomaticBrightnessController { mPreThresholdBrightness = mScreenAutoBrightness; } mScreenAutoBrightness = newScreenAutoBrightness; - mScreenBrighteningThreshold = clampScreenBrightness( - mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness)); - mScreenDarkeningThreshold = clampScreenBrightness( - mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness)); + if (isInIdleMode()) { + mScreenBrighteningThreshold = clampScreenBrightness( + mScreenBrightnessThresholdsIdle.getBrighteningThreshold( + newScreenAutoBrightness)); + mScreenDarkeningThreshold = clampScreenBrightness( + mScreenBrightnessThresholdsIdle.getDarkeningThreshold( + newScreenAutoBrightness)); + } else { + mScreenBrighteningThreshold = clampScreenBrightness( + mScreenBrightnessThresholds.getBrighteningThreshold( + newScreenAutoBrightness)); + mScreenDarkeningThreshold = clampScreenBrightness( + mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness)); + } if (sendUpdate) { mCallbacks.updateBrightness(); diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index cb04ddfd636d..2f67dddc1bf5 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -39,6 +39,7 @@ import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.window.ScreenCapture; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; @@ -181,7 +182,7 @@ final class ColorFade { // Set mPrepared here so if initialization fails, resources can be cleaned up. mPrepared = true; - final SurfaceControl.ScreenshotHardwareBuffer hardwareBuffer = captureScreen(); + final ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = captureScreen(); if (hardwareBuffer == null) { dismiss(); return false; @@ -508,7 +509,7 @@ final class ColorFade { } private boolean setScreenshotTextureAndSetViewport( - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer) { + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer) { if (!attachEglContext()) { return false; } @@ -559,8 +560,8 @@ final class ColorFade { } } - private SurfaceControl.ScreenshotHardwareBuffer captureScreen() { - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + private ScreenCapture.ScreenshotHardwareBuffer captureScreen() { + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = mDisplayManagerInternal.systemScreenshot(mDisplayId); if (screenshotBuffer == null) { Slog.e(TAG, "Failed to take screenshot. Buffer is null"); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 12b2f4773387..416518613568 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -188,8 +188,8 @@ import javax.xml.datatype.DatatypeConfigurationException; * <ambientLightHorizonLong>10001</ambientLightHorizonLong> * <ambientLightHorizonShort>2001</ambientLightHorizonShort> * - * <displayBrightnessChangeThresholds> - * <brighteningThresholds> + * <displayBrightnessChangeThresholds> // Thresholds for screen changes + * <brighteningThresholds> // Thresholds for active mode brightness changes. * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten. * </brighteningThresholds> * <darkeningThresholds> @@ -197,8 +197,8 @@ import javax.xml.datatype.DatatypeConfigurationException; * </darkeningThresholds> * </displayBrightnessChangeThresholds> * - * <ambientBrightnessChangeThresholds> - * <brighteningThresholds> + * <ambientBrightnessChangeThresholds> // Thresholds for lux changes + * <brighteningThresholds> // Thresholds for active mode brightness changes. * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten. * </brighteningThresholds> * <darkeningThresholds> @@ -206,6 +206,24 @@ import javax.xml.datatype.DatatypeConfigurationException; * </darkeningThresholds> * </ambientBrightnessChangeThresholds> * + * <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode + * <brighteningThresholds> // Thresholds for idle mode brightness changes. + * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten. + * </brighteningThresholds> + * <darkeningThresholds> + * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken. + * </darkeningThresholds> + * </displayBrightnessChangeThresholdsIdle> + * + * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode + * <brighteningThresholds> // Thresholds for idle mode brightness changes. + * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten. + * </brighteningThresholds> + * <darkeningThresholds> + * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken. + * </darkeningThresholds> + * </ambientBrightnessChangeThresholdsIdle> + * * </displayConfiguration> * } * </pre> @@ -319,9 +337,13 @@ public class DisplayDeviceConfig { private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS; private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is - private float mScreenDarkeningMinThreshold = 0.0f; // no minimum threshold for change in - private float mAmbientLuxBrighteningMinThreshold = 0.0f; // screen brightness or ambient - private float mAmbientLuxDarkeningMinThreshold = 0.0f; // brightness. + private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in + private float mScreenDarkeningMinThreshold = 0.0f; // screen brightness or ambient + private float mScreenDarkeningMinThresholdIdle = 0.0f; // brightness. + private float mAmbientLuxBrighteningMinThreshold = 0.0f; + private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f; + private float mAmbientLuxDarkeningMinThreshold = 0.0f; + private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f; private Spline mBrightnessToBacklightSpline; private Spline mBacklightToBrightnessSpline; private Spline mBacklightToNitsSpline; @@ -625,22 +647,76 @@ public class DisplayDeviceConfig { return mAmbientHorizonShort; } + /** + * The minimum value for the screen brightness increase to actually occur. + * @return float value in brightness scale of 0 - 1. + */ public float getScreenBrighteningMinThreshold() { return mScreenBrighteningMinThreshold; } + /** + * The minimum value for the screen brightness decrease to actually occur. + * @return float value in brightness scale of 0 - 1. + */ public float getScreenDarkeningMinThreshold() { return mScreenDarkeningMinThreshold; } + /** + * The minimum value for the screen brightness increase to actually occur while in idle screen + * brightness mode. + * @return float value in brightness scale of 0 - 1. + */ + public float getScreenBrighteningMinThresholdIdle() { + return mScreenBrighteningMinThresholdIdle; + } + + /** + * The minimum value for the screen brightness decrease to actually occur while in idle screen + * brightness mode. + * @return float value in brightness scale of 0 - 1. + */ + public float getScreenDarkeningMinThresholdIdle() { + return mScreenDarkeningMinThresholdIdle; + } + + /** + * The minimum value for the ambient lux increase for a screen brightness change to actually + * occur. + * @return float value in brightness scale of 0 - 1. + */ public float getAmbientLuxBrighteningMinThreshold() { return mAmbientLuxBrighteningMinThreshold; } + /** + * The minimum value for the ambient lux decrease for a screen brightness change to actually + * occur. + * @return float value in brightness scale of 0 - 1. + */ public float getAmbientLuxDarkeningMinThreshold() { return mAmbientLuxDarkeningMinThreshold; } + /** + * The minimum value for the ambient lux increase for a screen brightness change to actually + * occur while in idle screen brightness mode. + * @return float value in brightness scale of 0 - 1. + */ + public float getAmbientLuxBrighteningMinThresholdIdle() { + return mAmbientLuxBrighteningMinThresholdIdle; + } + + /** + * The minimum value for the ambient lux decrease for a screen brightness change to actually + * occur while in idle screen brightness mode. + * @return float value in brightness scale of 0 - 1. + */ + public float getAmbientLuxDarkeningMinThresholdIdle() { + return mAmbientLuxDarkeningMinThresholdIdle; + } + SensorData getAmbientLightSensor() { return mAmbientLightSensor; } @@ -745,9 +821,14 @@ public class DisplayDeviceConfig { + ", mAmbientHorizonLong=" + mAmbientHorizonLong + ", mAmbientHorizonShort=" + mAmbientHorizonShort + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold + + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold + + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold + + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold + + ", mAmbientLuxBrighteningMinThresholdIdle=" + + mAmbientLuxBrighteningMinThresholdIdle + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) @@ -1376,24 +1457,34 @@ public class DisplayDeviceConfig { private void loadBrightnessChangeThresholds(DisplayConfiguration config) { Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds(); Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds(); + Thresholds displayBrightnessThresholdsIdle = + config.getDisplayBrightnessChangeThresholdsIdle(); + Thresholds ambientBrightnessThresholdsIdle = + config.getAmbientBrightnessChangeThresholdsIdle(); + loadDisplayBrightnessThresholds(displayBrightnessThresholds); + loadAmbientBrightnessThresholds(ambientBrightnessThresholds); + loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle); + loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle); + } + + private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) { if (displayBrightnessThresholds != null) { BrightnessThresholds brighteningScreen = displayBrightnessThresholds.getBrighteningThresholds(); BrightnessThresholds darkeningScreen = displayBrightnessThresholds.getDarkeningThresholds(); - final BigDecimal screenBrighteningThreshold = brighteningScreen.getMinimum(); - final BigDecimal screenDarkeningThreshold = darkeningScreen.getMinimum(); - - if (screenBrighteningThreshold != null) { - mScreenBrighteningMinThreshold = screenBrighteningThreshold.floatValue(); + if (brighteningScreen != null && brighteningScreen.getMinimum() != null) { + mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue(); } - if (screenDarkeningThreshold != null) { - mScreenDarkeningMinThreshold = screenDarkeningThreshold.floatValue(); + if (darkeningScreen != null && darkeningScreen.getMinimum() != null) { + mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue(); } } + } + private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) { if (ambientBrightnessThresholds != null) { BrightnessThresholds brighteningAmbientLux = ambientBrightnessThresholds.getBrighteningThresholds(); @@ -1412,6 +1503,44 @@ public class DisplayDeviceConfig { } } + private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) { + if (idleDisplayBrightnessThresholds != null) { + BrightnessThresholds brighteningScreenIdle = + idleDisplayBrightnessThresholds.getBrighteningThresholds(); + BrightnessThresholds darkeningScreenIdle = + idleDisplayBrightnessThresholds.getDarkeningThresholds(); + + if (brighteningScreenIdle != null + && brighteningScreenIdle.getMinimum() != null) { + mScreenBrighteningMinThresholdIdle = + brighteningScreenIdle.getMinimum().floatValue(); + } + if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) { + mScreenDarkeningMinThresholdIdle = + darkeningScreenIdle.getMinimum().floatValue(); + } + } + } + + private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) { + if (idleAmbientBrightnessThresholds != null) { + BrightnessThresholds brighteningAmbientLuxIdle = + idleAmbientBrightnessThresholds.getBrighteningThresholds(); + BrightnessThresholds darkeningAmbientLuxIdle = + idleAmbientBrightnessThresholds.getDarkeningThresholds(); + + if (brighteningAmbientLuxIdle != null + && brighteningAmbientLuxIdle.getMinimum() != null) { + mAmbientLuxBrighteningMinThresholdIdle = + brighteningAmbientLuxIdle.getMinimum().floatValue(); + } + if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) { + mAmbientLuxDarkeningMinThresholdIdle = + darkeningAmbientLuxIdle.getMinimum().floatValue(); + } + } + } + private boolean thermalStatusIsValid(ThermalStatus value) { if (value == null) { return false; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e53ac379536b..3a037edf12eb 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -35,6 +35,7 @@ import static android.hardware.display.DisplayManagerGlobal.DisplayEvent; import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; +import static android.os.Process.ROOT_UID; import android.Manifest; import android.annotation.NonNull; @@ -118,6 +119,7 @@ import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; import android.window.DisplayWindowPolicyController; +import android.window.ScreenCapture; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -200,6 +202,8 @@ public final class DisplayManagerService extends SystemService { private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable"; private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top"; + private static final String PROP_USE_NEW_DISPLAY_POWER_CONTROLLER = + "persist.sys.use_new_display_power_controller"; private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000; // This value needs to be in sync with the threshold // in RefreshRateConfigs::getFrameRateDivisor. @@ -274,7 +278,7 @@ public final class DisplayManagerService extends SystemService { new CopyOnWriteArrayList<>(); /** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */ - private final SparseArray<DisplayPowerController> mDisplayPowerControllers = + private final SparseArray<DisplayPowerControllerInterface> mDisplayPowerControllers = new SparseArray<>(); /** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */ @@ -497,7 +501,6 @@ public final class DisplayManagerService extends SystemService { ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces(); mWideColorSpace = colorSpaces[1]; mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride(); - mSystemReady = false; } @@ -577,7 +580,7 @@ public final class DisplayManagerService extends SystemService { if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { return; } - final DisplayPowerController dpc = mDisplayPowerControllers.get( + final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get( logicalDisplay.getDisplayIdLocked()); if (dpc == null) { return; @@ -1170,6 +1173,10 @@ public final class DisplayManagerService extends SystemService { } private boolean validatePackageName(int uid, String packageName) { + // Root doesn't have a package name. + if (uid == ROOT_UID) { + return true; + } if (packageName != null) { String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); if (packageNames != null) { @@ -1557,7 +1564,7 @@ public final class DisplayManagerService extends SystemService { scheduleTraversalLocked(false); mPersistentDataStore.saveIfNeeded(); - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { dpc.onDisplayChanged(); } @@ -1575,7 +1582,8 @@ public final class DisplayManagerService extends SystemService { private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); - final DisplayPowerController dpc = mDisplayPowerControllers.removeReturnOld(displayId); + final DisplayPowerControllerInterface dpc = + mDisplayPowerControllers.removeReturnOld(displayId); if (dpc != null) { dpc.stop(); } @@ -1604,7 +1612,7 @@ public final class DisplayManagerService extends SystemService { mHandler.post(work); } final int displayId = display.getDisplayIdLocked(); - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { dpc.onDisplayChanged(); } @@ -1615,7 +1623,7 @@ public final class DisplayManagerService extends SystemService { private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); - final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { dpc.onDeviceStateTransition(); } @@ -1852,14 +1860,14 @@ public final class DisplayManagerService extends SystemService { if (userId != mCurrentUserId) { return; } - DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId); + DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId); if (dpc != null) { dpc.setBrightnessConfiguration(c); } } } - private DisplayPowerController getDpcFromUniqueIdLocked(String uniqueId) { + private DisplayPowerControllerInterface getDpcFromUniqueIdLocked(String uniqueId) { final DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId); final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayDevice); if (logicalDisplay != null) { @@ -1900,7 +1908,7 @@ public final class DisplayManagerService extends SystemService { final BrightnessConfiguration config = getBrightnessConfigForDisplayWithPdsFallbackLocked(uniqueId, userSerial); if (config != null) { - final DisplayPowerController dpc = mDisplayPowerControllers.get( + final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get( logicalDisplay.getDisplayIdLocked()); if (dpc != null) { dpc.setBrightnessConfiguration(config); @@ -2049,8 +2057,8 @@ public final class DisplayManagerService extends SystemService { return null; } - private SurfaceControl.ScreenshotHardwareBuffer systemScreenshotInternal(int displayId) { - final SurfaceControl.DisplayCaptureArgs captureArgs; + private ScreenCapture.ScreenshotHardwareBuffer systemScreenshotInternal(int displayId) { + final ScreenCapture.DisplayCaptureArgs captureArgs; synchronized (mSyncRoot) { final IBinder token = getDisplayToken(displayId); if (token == null) { @@ -2062,27 +2070,27 @@ public final class DisplayManagerService extends SystemService { } final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); - captureArgs = new SurfaceControl.DisplayCaptureArgs.Builder(token) + captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token) .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight()) .setUseIdentityTransform(true) .setCaptureSecureLayers(true) .setAllowProtected(true) .build(); } - return SurfaceControl.captureDisplay(captureArgs); + return ScreenCapture.captureDisplay(captureArgs); } - private SurfaceControl.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) { + private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) { synchronized (mSyncRoot) { final IBinder token = getDisplayToken(displayId); if (token == null) { return null; } - final SurfaceControl.DisplayCaptureArgs captureArgs = - new SurfaceControl.DisplayCaptureArgs.Builder(token) + final ScreenCapture.DisplayCaptureArgs captureArgs = + new ScreenCapture.DisplayCaptureArgs.Builder(token) .build(); - return SurfaceControl.captureDisplay(captureArgs); + return ScreenCapture.captureDisplay(captureArgs); } } @@ -2132,8 +2140,8 @@ public final class DisplayManagerService extends SystemService { void setAutoBrightnessLoggingEnabled(boolean enabled) { synchronized (mSyncRoot) { - final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( - Display.DEFAULT_DISPLAY); + final DisplayPowerControllerInterface displayPowerController = + mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY); if (displayPowerController != null) { displayPowerController.setAutoBrightnessLoggingEnabled(enabled); } @@ -2142,8 +2150,8 @@ public final class DisplayManagerService extends SystemService { void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { synchronized (mSyncRoot) { - final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( - Display.DEFAULT_DISPLAY); + final DisplayPowerControllerInterface displayPowerController = + mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY); if (displayPowerController != null) { displayPowerController.setDisplayWhiteBalanceLoggingEnabled(enabled); } @@ -2170,8 +2178,8 @@ public final class DisplayManagerService extends SystemService { void setAmbientColorTemperatureOverride(float cct) { synchronized (mSyncRoot) { - final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( - Display.DEFAULT_DISPLAY); + final DisplayPowerControllerInterface displayPowerController = + mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY); if (displayPowerController != null) { displayPowerController.setAmbientColorTemperatureOverride(cct); } @@ -2180,8 +2188,8 @@ public final class DisplayManagerService extends SystemService { void setDockedAndIdleEnabled(boolean enabled, int displayId) { synchronized (mSyncRoot) { - final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( - displayId); + final DisplayPowerControllerInterface displayPowerController = + mDisplayPowerControllers.get(displayId); if (displayPowerController != null) { displayPowerController.setAutomaticScreenBrightnessMode(enabled); } @@ -2554,10 +2562,19 @@ public final class DisplayManagerService extends SystemService { final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, display, mSyncRoot); - final DisplayPowerController displayPowerController = new DisplayPowerController( - mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, - mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, - () -> handleBrightnessChange(display)); + final DisplayPowerController displayPowerController; + + if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) { + displayPowerController = new DisplayPowerController( + mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, + mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, + () -> handleBrightnessChange(display)); + } else { + displayPowerController = new DisplayPowerController( + mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, + mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, + () -> handleBrightnessChange(display)); + } mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } @@ -3212,7 +3229,7 @@ public final class DisplayManagerService extends SystemService { uniqueId, userSerial); if (config == null) { // Get default configuration - DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId); + DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId); if (dpc != null) { config = dpc.getDefaultBrightnessConfiguration(); } @@ -3263,7 +3280,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { return dpc.getBrightnessInfo(); } @@ -3310,7 +3327,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { dpc.setBrightness(brightness); } @@ -3330,7 +3347,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { brightness = dpc.getScreenBrightnessSetting(); } @@ -3476,6 +3493,17 @@ public final class DisplayManagerService extends SystemService { Binder.restoreCallingIdentity(token); } } + + @Override + public void setDisplayIdToMirror(IBinder token, int displayId) { + synchronized (mSyncRoot) { + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); + if (mVirtualDisplayAdapter != null) { + mVirtualDisplayAdapter.setDisplayIdToMirror(token, + display == null ? Display.INVALID_DISPLAY : displayId); + } + } + } } private static boolean isValidBrightness(float brightness) { @@ -3534,7 +3562,7 @@ public final class DisplayManagerService extends SystemService { id).getPrimaryDisplayDeviceLocked(); final int flags = displayDevice.getDisplayDeviceInfoLocked().flags; if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { - final DisplayPowerController displayPowerController = + final DisplayPowerControllerInterface displayPowerController = mDisplayPowerControllers.get(id); ready &= displayPowerController.requestPowerState(request, waitForNegativeProximity); @@ -3564,12 +3592,12 @@ public final class DisplayManagerService extends SystemService { } @Override - public SurfaceControl.ScreenshotHardwareBuffer systemScreenshot(int displayId) { + public ScreenCapture.ScreenshotHardwareBuffer systemScreenshot(int displayId) { return systemScreenshotInternal(displayId); } @Override - public SurfaceControl.ScreenshotHardwareBuffer userScreenshot(int displayId) { + public ScreenCapture.ScreenshotHardwareBuffer userScreenshot(int displayId) { return userScreenshotInternal(displayId); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 247635017539..58a80e3f977b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -104,7 +104,7 @@ import java.io.PrintWriter; * slower by changing the "animator duration scale" option in Development Settings. */ final class DisplayPowerController implements AutomaticBrightnessController.Callbacks, - DisplayWhiteBalanceController.Callbacks { + DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface { private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked"; @@ -682,6 +682,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call /** * Returns true if the proximity sensor screen-off function is available. */ + @Override public boolean isProximitySensorAvailable() { return mProximitySensor != null; } @@ -693,6 +694,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * @param includePackage if false will null out the package name in events */ @Nullable + @Override public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents( @UserIdInt int userId, boolean includePackage) { if (mBrightnessTracker == null) { @@ -701,6 +703,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return mBrightnessTracker.getEvents(userId, includePackage); } + @Override public void onSwitchUser(@UserIdInt int newUserId) { handleSettingsChange(true /* userSwitch */); if (mBrightnessTracker != null) { @@ -709,6 +712,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Nullable + @Override public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats( @UserIdInt int userId) { if (mBrightnessTracker == null) { @@ -720,6 +724,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call /** * Persist the brightness slider events and ambient brightness stats to disk. */ + @Override public void persistBrightnessTrackerState() { if (mBrightnessTracker != null) { mBrightnessTracker.persistBrightnessTrackerState(); @@ -781,6 +786,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + @Override public BrightnessConfiguration getDefaultBrightnessConfiguration() { if (mAutomaticBrightnessController == null) { return null; @@ -794,6 +800,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * of each display need to be properly reflected in AutomaticBrightnessController. */ @GuardedBy("DisplayManagerService.mSyncRoot") + @Override public void onDisplayChanged() { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); if (device == null) { @@ -823,6 +830,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * This process involves turning off some displays so we need updatePowerState() to run and * calculate the new state. */ + @Override public void onDeviceStateTransition() { sendUpdatePowerState(); } @@ -833,6 +841,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * This method should be called when the DisplayPowerController is no longer in use; i.e. when * the {@link #mDisplayId display} has been removed. */ + @Override public void stop() { synchronized (mLock) { mStopped = true; @@ -989,6 +998,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels, screenDarkeningMinThreshold, screenBrighteningMinThreshold); + // Idle screen thresholds + float screenDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(); + float screenBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(); + HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels, + screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle); + + // Idle ambient thresholds + float ambientDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(); + float ambientBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(); + HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientThresholdLevels, ambientDarkeningMinThresholdIdle, + ambientBrighteningMinThresholdIdle); + long brighteningLightDebounce = mDisplayDeviceConfig .getAutoBrightnessBrighteningLightDebounce(); long darkeningLightDebounce = mDisplayDeviceConfig @@ -1024,7 +1052,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, - ambientBrightnessThresholds, screenBrightnessThresholds, mContext, + ambientBrightnessThresholds, screenBrightnessThresholds, + ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper, mDisplayDeviceConfig.getAmbientHorizonShort(), mDisplayDeviceConfig.getAmbientHorizonLong()); @@ -1063,6 +1092,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + @Override public void setAutomaticScreenBrightnessMode(boolean isIdle) { if (mAutomaticBrightnessController != null) { if (isIdle) { @@ -1766,27 +1796,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * Ignores the proximity sensor until the sensor state changes, but only if the sensor is * currently enabled and forcing the screen to be dark. */ + @Override public void ignoreProximitySensorUntilChanged() { mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY); } + @Override public void setBrightnessConfiguration(BrightnessConfiguration c) { Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c); msg.sendToTarget(); } + @Override public void setTemporaryBrightness(float brightness) { Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS, Float.floatToIntBits(brightness), 0 /*unused*/); msg.sendToTarget(); } + @Override public void setTemporaryAutoBrightnessAdjustment(float adjustment) { Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT, Float.floatToIntBits(adjustment), 0 /*unused*/); msg.sendToTarget(); } + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( @@ -2347,7 +2382,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj); } - float getScreenBrightnessSetting() { + @Override + public float getScreenBrightnessSetting() { float brightness = mBrightnessSetting.getBrightness(); if (Float.isNaN(brightness)) { brightness = mScreenBrightnessDefault; @@ -2362,7 +2398,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return clampScreenBrightnessForVr(brightnessFloat); } - void setBrightness(float brightnessValue) { + @Override + public void setBrightness(float brightnessValue) { // Update the setting, which will eventually call back into DPC to have us actually update // the display with the new value. mBrightnessSetting.setBrightness(brightnessValue); @@ -2511,6 +2548,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }; + @Override public void dump(final PrintWriter pw) { synchronized (mLock) { pw.println(); @@ -2919,7 +2957,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - void setAutoBrightnessLoggingEnabled(boolean enabled) { + @Override + public void setAutoBrightnessLoggingEnabled(boolean enabled) { if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.setLoggingEnabled(enabled); } @@ -2930,14 +2969,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call sendUpdatePowerState(); } - void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { + @Override + public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { if (mDisplayWhiteBalanceController != null) { mDisplayWhiteBalanceController.setLoggingEnabled(enabled); mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled); } } - void setAmbientColorTemperatureOverride(float cct) { + @Override + public void setAmbientColorTemperatureOverride(float cct) { if (mDisplayWhiteBalanceController != null) { mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); // The ambient color temperature override is only applied when the ambient color diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java new file mode 100644 index 000000000000..6677f358557d --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.pm.ParceledListSlice; +import android.hardware.display.AmbientBrightnessDayStats; +import android.hardware.display.BrightnessChangeEvent; +import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; + +import java.io.PrintWriter; + +/** + * An interface to manage the display's power state and brightness + */ +public interface DisplayPowerControllerInterface { + + /** + * Notified when the display is changed. We use this to apply any changes that might be needed + * when displays get swapped on foldable devices. + */ + void onDisplayChanged(); + + /** + * Unregisters all listeners and interrupts all running threads; halting future work. + * + * This method should be called when the DisplayPowerController is no longer in use; i.e. when + * the display has been removed. + */ + void stop(); + + /** + * Used to manage the displays preparing to transition from one device state to another. + */ + void onDeviceStateTransition(); + + /** + * Used to update the display's BrightnessConfiguration + * @param config The new BrightnessConfiguration + */ + void setBrightnessConfiguration(BrightnessConfiguration config); + + /** + * Used to set the ambient color temperature of the Display + * @param ambientColorTemperature The target ambientColorTemperature + */ + void setAmbientColorTemperatureOverride(float ambientColorTemperature); + + /** + * Used to decide the associated AutomaticBrightnessController's BrightnessMode + * @param isIdle Flag which represents if the Idle BrightnessMode is to be set + */ + void setAutomaticScreenBrightnessMode(boolean isIdle); + + /** + * Used to enable/disable the logging of the WhileBalance associated entities + * @param enabled Flag which represents if the logging is the be enabled + */ + void setDisplayWhiteBalanceLoggingEnabled(boolean enabled); + + /** + * Used to dump the state. + * @param writer The PrintWriter used to dump the state. + */ + void dump(PrintWriter writer); + + /** + * Used to get the ambient brightness stats + */ + ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId); + + /** + * Get the default brightness configuration + */ + BrightnessConfiguration getDefaultBrightnessConfiguration(); + + /** + * Set the screen brightness of the associated display + * @param brightness The value to which the brightness is to be set + */ + void setBrightness(float brightness); + + /** + * Checks if the proximity sensor is available + */ + boolean isProximitySensorAvailable(); + + /** + * Persist the brightness slider events and ambient brightness stats to disk. + */ + void persistBrightnessTrackerState(); + + /** + * Ignores the proximity sensor until the sensor state changes, but only if the sensor is + * currently enabled and forcing the screen to be dark. + */ + void ignoreProximitySensorUntilChanged(); + + /** + * Requests a new power state. + * + * @param request The requested power state. + * @param waitForNegativeProximity If true, issues a request to wait for + * negative proximity before turning the screen back on, + * assuming the screen was turned off by the proximity sensor. + * @return True if display is ready, false if there are important changes that must + * be made asynchronously. + */ + boolean requestPowerState(DisplayManagerInternal.DisplayPowerRequest request, + boolean waitForNegativeProximity); + + /** + * Sets up the temporary autobrightness adjustment when the user is yet to settle down to a + * value. + */ + void setTemporaryAutoBrightnessAdjustment(float adjustment); + + /** + * Gets the screen brightness setting + */ + float getScreenBrightnessSetting(); + + /** + * Sets up the temporary brightness for the associated display + */ + void setTemporaryBrightness(float brightness); + + /** + * Gets the associated {@link BrightnessInfo} + */ + BrightnessInfo getBrightnessInfo(); + + /** + * Get the {@link BrightnessChangeEvent}s for the specified user. + */ + ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(int userId, boolean hasUsageStats); + + /** + * Sets up the logging for the associated {@link AutomaticBrightnessController} + * @param enabled Flag to represent if the logging is to be enabled + */ + void setAutoBrightnessLoggingEnabled(boolean enabled); + + /** + * Handles the changes to be done to update the brightness when the user is changed + * @param newUserId The new userId + */ + void onSwitchUser(int newUserId); +} diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java index 7a932ce6d7cf..34134892552f 100644 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ b/services/core/java/com/android/server/display/HysteresisLevels.java @@ -18,15 +18,12 @@ package com.android.server.display; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; - import java.io.PrintWriter; import java.util.Arrays; /** * A helper class for handling access to illuminance hysteresis level values. */ -@VisibleForTesting public class HysteresisLevels { private static final String TAG = "HysteresisLevels"; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 38728ce106af..20b82c3e3c97 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -161,6 +161,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } } + void setDisplayIdToMirror(IBinder appToken, int displayId) { + VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); + if (device != null) { + device.setDisplayIdToMirror(displayId); + } + } + public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) { VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken); if (device != null) { @@ -313,6 +320,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter { return mDisplayIdToMirror; } + void setDisplayIdToMirror(int displayIdToMirror) { + if (mDisplayIdToMirror != displayIdToMirror) { + mDisplayIdToMirror = displayIdToMirror; + mInfo = null; + sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED); + sendTraversalRequestLocked(); + } + } + @Override public boolean isWindowManagerMirroringLocked() { return mIsWindowManagerMirroring; diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 6b568b74c405..63c0a88bf467 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -29,16 +29,13 @@ import android.view.InputEvent; import android.view.PointerIcon; import android.view.VerifiedInputEvent; -import com.android.internal.annotations.VisibleForTesting; - import java.util.List; /** * An interface for the native methods of InputManagerService. We use a public interface so that * this can be mocked for testing by Mockito. */ -@VisibleForTesting -public interface NativeInputManagerService { +interface NativeInputManagerService { void start(); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java index 15b209396a88..058c1c8efda5 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java @@ -325,12 +325,8 @@ public class ContextHubEventLogger { } } - /** - * Creates a string representation of the logged events - * - * @return the dumped events - */ - public synchronized String dump() { + @Override + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Nanoapp Loads:"); sb.append(System.lineSeparator()); @@ -368,9 +364,4 @@ public class ContextHubEventLogger { } return sb.toString(); } - - @Override - public String toString() { - return dump(); - } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 4ca0ea6e0e95..7ce1017ba4a8 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -1054,7 +1054,7 @@ public class ContextHubService extends IContextHubService.Stub { pw.println(""); pw.println("=================== EVENTS ===================="); - pw.println(ContextHubEventLogger.getInstance().dump()); + pw.println(ContextHubEventLogger.getInstance()); // dump eventLog } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0fac808fb13b..4d55d4e545ee 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -1786,8 +1786,8 @@ abstract public class ManagedServices { * from receiving events from the profile. */ public boolean isPermittedForProfile(int userId) { - if (!mUserProfiles.canProfileUseBoundServices(userId)) { - return false; + if (!mUserProfiles.isProfileUser(userId)) { + return true; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE); @@ -1862,16 +1862,16 @@ abstract public class ManagedServices { } } - public boolean canProfileUseBoundServices(int userId) { + public boolean isProfileUser(int userId) { synchronized (mCurrentProfiles) { UserInfo user = mCurrentProfiles.get(userId); if (user == null) { return false; } if (user.isManagedProfile() || user.isCloneProfile()) { - return false; + return true; } - return true; + return false; } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0b4f736445ab..4b18add39999 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1829,7 +1829,7 @@ public class NotificationManagerService extends SystemService { } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { // reload per-user settings mSettingsObserver.update(null); // Refresh managed services @@ -1843,7 +1843,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); if (userId != USER_NULL) { mUserProfiles.updateCache(context); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { allowDefaultApprovedServices(userId); } } @@ -1861,7 +1861,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); mAssistants.onUserUnlocked(userId); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); mZenModeHelper.onUserUnlocked(userId); @@ -1978,34 +1978,39 @@ public class NotificationManagerService extends SystemService { return (haystack & needle) != 0; } - public boolean isInLockDownMode() { - return mIsInLockDownMode; + // Return whether the user is in lockdown mode. + // If the flag is not set, we assume the user is not in lockdown. + public boolean isInLockDownMode(int userId) { + return mUserInLockDownMode.get(userId, false); } @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) { + // Nothing happens if the lockdown mode of userId keeps the same. + if (userInLockDownModeNext == isInLockDownMode(userId)) { return; } - if (isInLockDownModeNext) { - cancelNotificationsWhenEnterLockDownMode(); + // When the lockdown mode is changed, we perform the following steps. + // If the userInLockDownModeNext is true, all the function calls to + // notifyPostedLocked and notifyRemovedLocked will not be executed. + // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked + // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked. + // So we shall call cancelNotificationsWhenEnterLockDownMode before + // we set mUserInLockDownMode as true. + // On the other hand, if the userInLockDownModeNext is false, we shall call + // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode + if (userInLockDownModeNext) { + cancelNotificationsWhenEnterLockDownMode(userId); } - // 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; + mUserInLockDownMode.put(userId, userInLockDownModeNext); - if (!isInLockDownModeNext) { - postNotificationsWhenExitLockDownMode(); + if (!userInLockDownModeNext) { + postNotificationsWhenExitLockDownMode(userId); } } } @@ -9712,11 +9717,14 @@ public class NotificationManagerService extends SystemService { } } - private void cancelNotificationsWhenEnterLockDownMode() { + private void cancelNotificationsWhenEnterLockDownMode(int userId) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); + if (rec.getUser().getIdentifier() != userId) { + continue; + } mListeners.notifyRemovedLocked(rec, REASON_LOCKDOWN, rec.getStats()); } @@ -9724,14 +9732,23 @@ public class NotificationManagerService extends SystemService { } } - private void postNotificationsWhenExitLockDownMode() { + private void postNotificationsWhenExitLockDownMode(int userId) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); + // Set the delay to spread out the burst of notifications. + long delay = 0; for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - mListeners.notifyPostedLocked(rec, rec); + if (rec.getUser().getIdentifier() != userId) { + continue; + } + mHandler.postDelayed(() -> { + synchronized (mNotificationLock) { + mListeners.notifyPostedLocked(rec, rec); + } + }, delay); + delay += 20; } - } } @@ -9910,6 +9927,9 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); + if (isInLockDownMode(record.getUser().getIdentifier())) { + continue; + } if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) { continue; } @@ -9951,8 +9971,8 @@ public class NotificationManagerService extends SystemService { rankings.toArray(new NotificationListenerService.Ranking[0])); } - boolean isInLockDownMode() { - return mStrongAuthTracker.isInLockDownMode(); + boolean isInLockDownMode(int userId) { + return mStrongAuthTracker.isInLockDownMode(userId); } boolean hasCompanionDevice(ManagedServiceInfo info) { @@ -11014,7 +11034,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { - if (isInLockDownMode()) { + if (isInLockDownMode(r.getUser().getIdentifier())) { return; } @@ -11120,7 +11140,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { - if (isInLockDownMode()) { + if (isInLockDownMode(r.getUser().getIdentifier())) { return; } @@ -11169,10 +11189,6 @@ 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/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index ba7143876917..baa471c297d3 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -23,7 +23,9 @@ import static android.content.Intent.ACTION_PACKAGE_CHANGED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; +import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_REASON; +import static android.content.Intent.EXTRA_USER_ID; import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED; import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED; import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED; @@ -39,6 +41,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -56,6 +59,7 @@ import android.content.pm.overlay.OverlayPaths; import android.content.res.ApkAssets; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Environment; import android.os.FabricatedOverlayInternal; import android.os.HandlerThread; @@ -1009,7 +1013,9 @@ public final class OverlayManagerService extends SystemService { } opti++; - if ("-h".equals(opt)) { + if ("-a".equals(opt)) { + // dumpsys will pass in -a; silently ignore it + } else if ("-h".equals(opt)) { pw.println("dump [-h] [--verbose] [--user USER_ID] [[FIELD] PACKAGE]"); pw.println(" Print debugging information about the overlay manager."); pw.println(" With optional parameter PACKAGE, limit output to the specified"); @@ -1098,6 +1104,7 @@ public final class OverlayManagerService extends SystemService { private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName, int realUserId) throws SecurityException { OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId); + if (overlayInfo == null) { throw new IllegalArgumentException("Unable to retrieve overlay information for " + overlay); @@ -1416,20 +1423,41 @@ public final class OverlayManagerService extends SystemService { private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages, final int userId) { + final ActivityManagerInternal amInternal = + LocalServices.getService(ActivityManagerInternal.class); CollectionUtils.forEach(targetPackages, target -> { final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, Uri.fromParts("package", target, null)); intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - try { - ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, - null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); - } catch (RemoteException e) { - Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e); - } + intent.putExtra(EXTRA_PACKAGE_NAME, target); + intent.putExtra(EXTRA_USER_ID, userId); + amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */, + false /* serialized */, userId, null /* appIdAllowList */, + OverlayManagerService::filterReceiverAccess, null /* bOptions */); }); } /** + * A callback from the broadcast queue to determine whether the intent + * {@link Intent#ACTION_OVERLAY_CHANGED} is visible to the receiver. + * + * @param callingUid The receiver's uid. + * @param extras The extras of intent that contains {@link Intent#EXTRA_PACKAGE_NAME} and + * {@link Intent#EXTRA_USER_ID} to check. + * @return {@code null} if the intent is not visible to the receiver. + */ + @Nullable + private static Bundle filterReceiverAccess(int callingUid, @NonNull Bundle extras) { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final int userId = extras.getInt(EXTRA_USER_ID); + if (LocalServices.getService(PackageManagerInternal.class).filterAppAccess( + packageName, callingUid, userId, false /* filterUninstalled */)) { + return null; + } + return extras; + } + + /** * Tell the activity manager to tell a set of packages to reload their * resources. */ diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index ae305cacd2c4..8e672c3b32c5 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -519,6 +519,7 @@ final class OverlayManagerServiceImpl { throws OperationFailedException { final OverlayIdentifier overlayIdentifier = new OverlayIdentifier( info.packageName, info.overlayName); + final Set<PackageAndUser> updatedTargets = new ArraySet<>(); OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId); if (oi != null) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 66a97b3b9a89..218d9d1fd085 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1344,9 +1344,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } private String getDeviceOwnerDeletedPackageMsg() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(PACKAGE_DELETED_BY_DO, - () -> mContext.getString(R.string.package_deleted_device_owner)); + final long ident = Binder.clearCallingIdentity(); + try { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getResources().getString(PACKAGE_DELETED_BY_DO, + () -> mContext.getString(R.string.package_deleted_device_owner)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 34d6d292dd19..65e7ce1466dd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -465,6 +465,20 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { filterCallingUid); } + /** + * @deprecated similar to {@link resolveIntent} but limits the matches to exported components. + */ + @Override + @Deprecated + public final ResolveInfo resolveIntentExported(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId, + boolean resolveForStart, int filterCallingUid) { + return getResolveIntentHelper().resolveIntentInternal(snapshot(), + intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart, + filterCallingUid, true); + } + @Override @Deprecated public final ResolveInfo resolveService(Intent intent, String resolvedType, diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index c2fd637cfbd0..fada5778b634 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -52,6 +52,7 @@ import android.util.Slog; import com.android.internal.app.ResolverActivity; import com.android.internal.util.ArrayUtils; +import com.android.server.am.ActivityManagerService; import com.android.server.compat.PlatformCompat; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; @@ -100,6 +101,38 @@ final class ResolveIntentHelper { mInstantAppInstallerActivitySupplier = instantAppInstallerActivitySupplier; } + private static void filterNonExportedComponents(Intent intent, int filterCallingUid, + List<ResolveInfo> query, PlatformCompat platformCompat, Computer computer) { + if (query == null + || intent.getPackage() != null + || intent.getComponent() != null + || ActivityManager.canAccessUnexportedComponents(filterCallingUid)) { + return; + } + AndroidPackage caller = computer.getPackage(filterCallingUid); + String callerPackage = caller == null ? "Not specified" : caller.getPackageName(); + for (int i = query.size() - 1; i >= 0; i--) { + if (!query.get(i).getComponentInfo().exported) { + if (!platformCompat.isChangeEnabledByUid( + ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS, + filterCallingUid)) { + Slog.w(TAG, "Non-exported component not filtered out " + + "(will be filtered out once the app targets U+)- intent: " + + intent.getAction() + ", component: " + + query.get(i).getComponentInfo() + .getComponentName().flattenToShortString() + + ", starter: " + callerPackage); + return; + } + Slog.w(TAG, "Non-exported component filtered out - intent: " + + intent.getAction() + ", component: " + + query.get(i).getComponentInfo().getComponentName().flattenToShortString() + + ", starter: " + callerPackage); + query.remove(i); + } + } + } + /** * Normally instant apps can only be resolved when they're visible to the caller. * However, if {@code resolveForStart} is {@code true}, all instant apps are visible @@ -109,6 +142,20 @@ final class ResolveIntentHelper { @PackageManager.ResolveInfoFlagsBits long flags, @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart, int filterCallingUid) { + return resolveIntentInternal(computer, intent, resolvedType, flags, + privateResolveFlags, userId, resolveForStart, filterCallingUid, false); + } + + /** + * Normally instant apps can only be resolved when they're visible to the caller. + * However, if {@code resolveForStart} is {@code true}, all instant apps are visible + * since we need to allow the system to start any installed application. + * Allows picking exported components only. + */ + public ResolveInfo resolveIntentInternal(Computer computer, Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId, + boolean resolveForStart, int filterCallingUid, boolean exportedComponentsOnly) { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent"); @@ -124,6 +171,10 @@ final class ResolveIntentHelper { final List<ResolveInfo> query = computer.queryIntentActivitiesInternal(intent, resolvedType, flags, privateResolveFlags, filterCallingUid, userId, resolveForStart, true /*allowDynamicSplits*/); + if (exportedComponentsOnly) { + filterNonExportedComponents(intent, filterCallingUid, query, + mPlatformCompat, computer); + } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); final boolean queryMayBeFiltered = diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 4f5fd023d470..b62024996424 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -328,8 +328,10 @@ public abstract class UserManagerInternal { * <p>On most devices this call will be a no-op, but it will be used on devices that support * multiple users on multiple displays (like automotives with passenger displays). * - * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} when a user is - * started and it doesn't validate if the display exists. + * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to + * check it. In fact, one of the intended clients for this method is + * {@code DisplayManagerService}, which will call it when a virtual display is created (another + * client is {@code UserController}, which will call it when a user is started). * */ public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId); @@ -340,8 +342,8 @@ public abstract class UserManagerInternal { * <p>On most devices this call will be a no-op, but it will be used on devices that support * multiple users on multiple displays (like automotives with passenger displays). * - * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} when a user is - * stopped. + * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user + * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed). */ public abstract void unassignUserFromDisplay(@UserIdInt int userId); @@ -361,11 +363,14 @@ public abstract class UserManagerInternal { * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the * user is not assigned to any display. * - * <p>The current foreground user is associated with the + * <p>The current foreground user and its running profiles are associated with the * {@link android.view.Display#DEFAULT_DISPLAY default display}, while other users would only be - * assigned to a display if they were started with - * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}. If the user is a profile - * and is running, it's assigned to its parent display. + * assigned to a display if a call to {@link #assignUserToDisplay(int, int)} is made for such + * user / display combination (for example, if the user was started with + * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}, {@code UserController} + * would make such call). + * + * <p>If the user is a profile and is running, it's assigned to its parent display. */ public abstract int getDisplayAssignedToUser(@UserIdInt int userId); @@ -375,8 +380,11 @@ public abstract class UserManagerInternal { * associated with the display. * * <p>The {@link android.view.Display#DEFAULT_DISPLAY default display} is always assigned to - * the current foreground user, while other displays would be associated with the user that was - * started with {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}. + * the current foreground user, while other displays would only be associated with users through + * a explicit {@link #assignUserToDisplay(int, int)} call with that user / display combination + * (for example, if the user was started with + * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}, {@code UserController} + * would make such call). */ public abstract @UserIdInt int getUserAssignedToDisplay(int displayId); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 07ec80bcd603..3b3e1db0f35d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1785,18 +1785,10 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting int getUserAssignedToDisplay(int displayId) { - if (displayId == Display.DEFAULT_DISPLAY) { + if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) { return getCurrentUserId(); } - if (!mUsersOnSecondaryDisplaysEnabled) { - int currentUserId = getCurrentUserId(); - Slogf.w(LOG_TAG, "getUsersAssignedToDisplay(%d) called with non-DEFAULT_DISPLAY on " - + "system that doesn't support that; returning current user (%d)", displayId, - currentUserId); - return currentUserId; - } - synchronized (mUsersOnSecondaryDisplays) { for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) { if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) { @@ -6844,20 +6836,22 @@ public class UserManagerService extends IUserManager.Stub { // Check if display is available for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) { - // Make sure display is not used by other users... - // TODO(b/240736142); currently, if a user was started in a display, it - // would need to be stopped first, so "switching" a user on secondary - // diplay requires 2 non-atomic operations (stop and start). Once this logic - // is refactored, it should be atomic. - if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) { - throw new IllegalStateException("Cannot assign " + userId + " to " - + "display " + displayId + " as it's already assigned to " - + "user " + mUsersOnSecondaryDisplays.keyAt(i)); + int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i); + int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i); + if (DBG_MUMD) { + Slogf.d(LOG_TAG, "%d: assignedUserId=%d, assignedDisplayId=%d", + i, assignedUserId, assignedDisplayId); + } + if (displayId == assignedDisplayId) { + throw new IllegalStateException("Cannot assign user " + userId + " to " + + "display " + displayId + " because such display is already " + + "assigned to user " + assignedUserId); + } + if (userId == assignedUserId) { + throw new IllegalStateException("Cannot assign user " + userId + " to " + + "display " + displayId + " because such user is as already " + + "assigned to display " + assignedDisplayId); } - // TODO(b/239982558) also check that user is not already assigned to other - // display (including 0). That would be harder to tested under CTS though - // (for example, would need to add a new AM method to start user in bg on - // main display), so it's better to test on unit tests } if (DBG_MUMD) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e332ac765633..675819a77243 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3052,6 +3052,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } break; + case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: + Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in" + + " interceptKeyBeforeQueueing"); + return key_consumed; } if (isValidGlobalKey(keyCode) @@ -4104,6 +4111,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { result &= ~ACTION_PASS_TO_USER; break; } + case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: { + // TODO(go/android-stylus-buttons): Handle stylus button presses. + result &= ~ACTION_PASS_TO_USER; + break; + } } if (useHapticFeedback) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java index 05d92beed11f..b0f03ef48e7e 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java @@ -17,6 +17,7 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.soundtrigger.ModelParameterRange; import android.media.soundtrigger.PhraseRecognitionEvent; import android.media.soundtrigger.PhraseSoundModel; @@ -289,43 +290,60 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal * they were pushed. * <li>Events can be flushed via {@link #flush()}. This will block until all events pushed prior * to this call have been fully processed. + * TODO(b/246584464) Remove and replace with Handler (and other concurrency fixes). * </ul> */ - private static class CallbackThread { + private static class CallbackThread implements Runnable { private final Queue<Runnable> mList = new LinkedList<>(); private int mPushCount = 0; private int mProcessedCount = 0; - + private boolean mQuitting = false; + private final Thread mThread; /** * Ctor. Starts the thread. */ CallbackThread() { - new Thread(() -> { - try { - while (true) { - pop().run(); - synchronized (mList) { - mProcessedCount++; - mList.notifyAll(); - } + mThread = new Thread(this , "STHAL Concurrent Capture Handler Callback"); + mThread.start(); + } + /** + * Consume items in the queue until quit is called. + */ + public void run() { + try { + while (true) { + Runnable toRun = pop(); + if (toRun == null) { + // There are no longer any runnables to run, + // and quit() has been called. + return; + } + toRun.run(); + synchronized (mList) { + mProcessedCount++; + mList.notifyAll(); } - } catch (InterruptedException e) { - // If interrupted, exit. } - }).start(); + } catch (InterruptedException e) { + // If interrupted, exit. + // Note, this is dangerous wrt to flush. + } } - /** * Push a new runnable to the queue, with no deduping. + * If quit has been called, the runnable will not be pushed. * * @param runnable The runnable to push. + * @return If the runnable was successfully pushed. */ - void push(Runnable runnable) { + boolean push(Runnable runnable) { synchronized (mList) { + if (mQuitting) return false; mList.add(runnable); mPushCount++; mList.notifyAll(); } + return true; } /** @@ -343,11 +361,26 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal } } - private Runnable pop() throws InterruptedException { + /** + * Quit processing after the queue is cleared. + * All subsequent calls to push will fail. + * Note, this does not flush. + */ + void quit() { + synchronized(mList) { + mQuitting = true; + mList.notifyAll(); + } + } + + // Returns the next runnable when available. + // Returns null iff the list is empty and quit has been called. + private @Nullable Runnable pop() throws InterruptedException { synchronized (mList) { - while (mList.isEmpty()) { + while (mList.isEmpty() && !mQuitting) { mList.wait(); } + if (mList.isEmpty() && mQuitting) return null; return mList.remove(); } } @@ -372,6 +405,7 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal public void detach() { mDelegate.detach(); mNotifier.unregisterListener(this); + mCallbackThread.quit(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java index 227424ea1780..b817821b48dc 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java @@ -150,6 +150,8 @@ public class SoundTriggerHalWatchdog implements ISoundTriggerHal { @Override public void detach() { mUnderlying.detach(); + // We should no longer have any pending calls + mTimer.quit(); } private class Watchdog implements AutoCloseable { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java index bfcc7d840662..fe91e66b5769 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java @@ -18,9 +18,7 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; import android.os.Handler; -import android.os.Looper; - -import java.util.concurrent.atomic.AtomicReference; +import android.os.HandlerThread; /** * A simple timer, similar to java.util.Timer, but using the "uptime clock". @@ -33,58 +31,45 @@ import java.util.concurrent.atomic.AtomicReference; * task.cancel(); */ class UptimeTimer { - private Handler mHandler = null; + private final Handler mHandler; + private final HandlerThread mHandlerThread; interface Task { void cancel(); } UptimeTimer(String threadName) { - new Thread(this::threadFunc, threadName).start(); - synchronized (this) { - while (mHandler == null) { - try { - wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } + mHandlerThread = new HandlerThread(threadName); + mHandlerThread.start(); + // Blocks until looper init + mHandler = new Handler(mHandlerThread.getLooper()); } + // Note, this method is not internally synchronized. + // This is safe since Handlers are internally synchronized. Task createTask(@NonNull Runnable runnable, long uptimeMs) { - TaskImpl task = new TaskImpl(runnable); - mHandler.postDelayed(task, uptimeMs); + Object token = new Object(); + TaskImpl task = new TaskImpl(mHandler, token); + mHandler.postDelayed(runnable, token, uptimeMs); return task; } - private void threadFunc() { - Looper.prepare(); - synchronized (this) { - mHandler = new Handler(Looper.myLooper()); - notifyAll(); - } - Looper.loop(); + void quit() { + mHandlerThread.quitSafely(); } - private static class TaskImpl implements Task, Runnable { - private AtomicReference<Runnable> mRunnable = new AtomicReference<>(); + private static class TaskImpl implements Task { + private final Handler mHandler; + private final Object mToken; - TaskImpl(@NonNull Runnable runnable) { - mRunnable.set(runnable); + public TaskImpl(Handler handler, Object token) { + mHandler = handler; + mToken = token; } @Override public void cancel() { - mRunnable.set(null); + mHandler.removeCallbacksAndMessages(mToken); } - - @Override - public void run() { - Runnable runnable = mRunnable.get(); - if (runnable != null) { - runnable.run(); - } - } - } + }; } diff --git a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java index 1fb7cf5249cc..421f7ce9cf58 100644 --- a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java +++ b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java @@ -260,10 +260,11 @@ public final class GnssTimeUpdateService extends Binder { private void suggestGnssTime(LocationTime locationTime) { logDebug("suggestGnssTime()"); - long gnssTime = locationTime.getTime(); + long gnssUnixEpochTimeMillis = locationTime.getUnixEpochTimeMillis(); long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L; - TimestampedValue<Long> timeSignal = new TimestampedValue<>(elapsedRealtimeMs, gnssTime); + TimestampedValue<Long> timeSignal = + new TimestampedValue<>(elapsedRealtimeMs, gnssUnixEpochTimeMillis); mLastSuggestedGnssTime = timeSignal; GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal); diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java index 3f4def6d60ab..41373cd6bbe6 100644 --- a/services/core/java/com/android/server/utils/AlarmQueue.java +++ b/services/core/java/com/android/server/utils/AlarmQueue.java @@ -34,6 +34,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.Comparator; import java.util.PriorityQueue; import java.util.function.Predicate; @@ -52,6 +53,11 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { private static final boolean DEBUG = false; private static final long NOT_SCHEDULED = -1; + /** + * The threshold used to consider a new trigger time to be significantly different from the + * currently used trigger time. + */ + private static final long SIGNIFICANT_TRIGGER_TIME_CHANGE_THRESHOLD_MS = MINUTE_IN_MILLIS; /** * Internal priority queue for each key's alarm, ordered by the time the alarm should go off. @@ -59,7 +65,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { */ private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> { AlarmPriorityQueue() { - super(1, (o1, o2) -> (int) (o1.second - o2.second)); + super(1, Comparator.comparingLong(o -> o.second)); } /** @@ -306,8 +312,10 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { // earlier but not significantly so, then we essentially delay the check for some // apps by up to a minute. // 3. The alarm is after the current alarm. + final long timeShiftThresholdMs = + Math.min(SIGNIFICANT_TRIGGER_TIME_CHANGE_THRESHOLD_MS, mMinTimeBetweenAlarmsMs); if (mTriggerTimeElapsed == NOT_SCHEDULED - || nextTriggerTimeElapsed < mTriggerTimeElapsed - MINUTE_IN_MILLIS + || nextTriggerTimeElapsed < mTriggerTimeElapsed - timeShiftThresholdMs || mTriggerTimeElapsed < nextTriggerTimeElapsed) { if (DEBUG) { Slog.d(TAG, "Scheduling alarm at " + nextTriggerTimeElapsed diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java index bbc541438c82..e88ac63615ca 100644 --- a/services/core/java/com/android/server/utils/Slogf.java +++ b/services/core/java/com/android/server/utils/Slogf.java @@ -162,6 +162,21 @@ public final class Slogf { } /** + * Logs a {@link Log.VEBOSE} message with an exception + * + * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging + * is enabled for the given {@code tag}, but the compiler will still create an intermediate + * array of the objects for the {@code vargars}, which could affect garbage collection. So, if + * you're calling this method in a critical path, make sure to explicitly do the check before + * calling it. + */ + public static void v(String tag, Exception exception, String format, @Nullable Object... args) { + if (!isLoggable(tag, Log.VERBOSE)) return; + + v(tag, getMessage(format, args), exception); + } + + /** * Logs a {@link Log.DEBUG} message. * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging is @@ -177,6 +192,21 @@ public final class Slogf { } /** + * Logs a {@link Log.DEBUG} message with an exception + * + * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging + * is enabled for the given {@code tag}, but the compiler will still create an intermediate + * array of the objects for the {@code vargars}, which could affect garbage collection. So, if + * you're calling this method in a critical path, make sure to explicitly do the check before + * calling it. + */ + public static void d(String tag, Exception exception, String format, @Nullable Object... args) { + if (!isLoggable(tag, Log.DEBUG)) return; + + d(tag, getMessage(format, args), exception); + } + + /** * Logs a {@link Log.INFO} message. * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging is @@ -192,6 +222,21 @@ public final class Slogf { } /** + * Logs a {@link Log.INFO} message with an exception + * + * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging + * is enabled for the given {@code tag}, but the compiler will still create an intermediate + * array of the objects for the {@code vargars}, which could affect garbage collection. So, if + * you're calling this method in a critical path, make sure to explicitly do the check before + * calling it. + */ + public static void i(String tag, Exception exception, String format, @Nullable Object... args) { + if (!isLoggable(tag, Log.INFO)) return; + + i(tag, getMessage(format, args), exception); + } + + /** * Logs a {@link Log.WARN} message. * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is @@ -220,6 +265,7 @@ public final class Slogf { w(tag, getMessage(format, args), exception); } + /** * Logs a {@link Log.ERROR} message. * diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index fcf0a904b808..6012993db916 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -224,8 +224,6 @@ final class VibrationSettings { public void onSystemReady() { PowerManagerInternal pm = LocalServices.getService(PowerManagerInternal.class); - VirtualDeviceManagerInternal vdm = LocalServices.getService( - VirtualDeviceManagerInternal.class); AudioManager am = mContext.getSystemService(AudioManager.class); int ringerMode = am.getRingerModeInternal(); @@ -263,9 +261,11 @@ final class VibrationSettings { } }); - if (vdm != null){ - vdm.registerVirtualDisplayListener(mVirtualDeviceListener); - vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener); + VirtualDeviceManagerInternal vdm = LocalServices.getService( + VirtualDeviceManagerInternal.class); + if (vdm != null) { + vdm.registerVirtualDisplayListener(mVirtualDeviceListener); + vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener); } registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER); @@ -791,33 +791,35 @@ final class VibrationSettings { mAppsOnVirtualDevice.clear(); mAppsOnVirtualDevice.addAll(allRunningUids); } - - } /** * @param uid: uid of the calling app. * @param displayId: the id of a Display. * @return Returns true if: - * 1) the displayId is valid, and it's owned by a virtual device - * 2) the displayId is invalid, and the calling app (uid) is running on a virtual device. + * <ul> + * <li> the displayId is valid, and it's owned by a virtual device.</li> + * <li> the displayId is invalid, and the calling app (uid) is running on a virtual + * device.</li> + * </ul> */ public boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + // The default display is the primary physical display on the phone. + return false; + } + synchronized (mLock) { - switch (displayId) { - case Display.DEFAULT_DISPLAY: - // The default display is the primary physical display on the phone. - return false; - case Display.INVALID_DISPLAY: - // There is no Display object associated with the Context of calling - // {@link SystemVibratorManager}, checking the calling UID instead. - return mAppsOnVirtualDevice.contains(uid); - default: - // Other valid display IDs representing valid logical displays will be - // checked - // against the active virtual displays set built with the registered - // {@link VirtualDisplayListener}. - return mVirtualDisplays.contains(displayId); + if (displayId == Display.INVALID_DISPLAY) { + // There is no Display object associated with the Context of calling + // {@link SystemVibratorManager}, checking the calling UID instead. + return mAppsOnVirtualDevice.contains(uid); + } else { + // Other valid display IDs representing valid logical displays will be + // checked + // against the active virtual displays set built with the registered + // {@link VirtualDisplayListener}. + return mVirtualDisplays.contains(displayId); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 16b5ee54a5ba..87ff7d3fa30b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1522,8 +1522,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A this.task = newTask; if (shouldStartChangeTransition(newParent, oldParent)) { - // Animate change transition on TaskFragment level to get the correct window crop. - newParent.initializeChangeTransition(getBounds(), getSurfaceControl()); + if (mTransitionController.isShellTransitionsEnabled()) { + // For Shell transition, call #initializeChangeTransition directly to take the + // screenshot at the Activity level. And Shell will be in charge of handling the + // surface reparent and crop. + initializeChangeTransition(getBounds()); + } else { + // For legacy app transition, we want to take a screenshot of the Activity surface, + // but animate the change transition on TaskFragment level to get the correct window + // crop. + newParent.initializeChangeTransition(getBounds(), getSurfaceControl()); + } } super.onParentChanged(newParent, oldParent); @@ -8024,11 +8033,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Horizontal position int offsetX = 0; if (parentBounds.width() != screenResolvedBounds.width()) { - if (screenResolvedBounds.width() >= parentAppBounds.width()) { - // If resolved bounds overlap with insets, center within app bounds. - offsetX = getCenterOffset( - parentAppBounds.width(), screenResolvedBounds.width()); - } else { + if (screenResolvedBounds.width() <= parentAppBounds.width()) { float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier( newParentConfiguration); @@ -8040,11 +8045,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Vertical position int offsetY = 0; if (parentBounds.height() != screenResolvedBounds.height()) { - if (screenResolvedBounds.height() >= parentAppBounds.height()) { - // If resolved bounds overlap with insets, center within app bounds. - offsetY = getCenterOffset( - parentAppBounds.height(), screenResolvedBounds.height()); - } else { + if (screenResolvedBounds.height() <= parentAppBounds.height()) { float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier( newParentConfiguration); @@ -8062,6 +8063,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A offsetBounds(resolvedConfig, offsetX, offsetY); } + // If the top is aligned with parentAppBounds add the vertical insets back so that the app + // content aligns with the status bar + if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) { + resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top; + if (mSizeCompatBounds != null) { + mSizeCompatBounds.top = parentBounds.top; + } + } + // Since bounds has changed, the configuration needs to be computed accordingly. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } @@ -8439,7 +8449,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Above coordinates are in "@" space, now place "*" and "#" to screen space. final boolean fillContainer = resolvedBounds.equals(containingBounds); final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; - final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; + final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { @@ -8785,24 +8795,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Also account for the insets (e.g. display cutouts, navigation bar), which will be // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise, - // the app bounds would end up too small. + // the app bounds would end up too small. To achieve this we will also add clippable insets + // when the corresponding dimension fully fills the parent + int right = activityWidth + containingAppBounds.left; + int left = containingAppBounds.left; if (right >= containingAppBounds.right) { - right += containingBounds.right - containingAppBounds.right; + right = containingBounds.right; + left = containingBounds.left; } int bottom = activityHeight + containingAppBounds.top; + int top = containingAppBounds.top; if (bottom >= containingAppBounds.bottom) { - bottom += containingBounds.bottom - containingAppBounds.bottom; + bottom = containingBounds.bottom; + top = containingBounds.top; } - outBounds.set(containingBounds.left, containingBounds.top, right, bottom); - - // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the - // container app bounds. Otherwise the entire container bounds are available. - if (!outBounds.equals(containingBounds)) { - // The horizontal position should not cover insets (e.g. display cutout). - outBounds.left = containingAppBounds.left; - } - + outBounds.set(left, top, right, bottom); return true; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index dc91c1597128..28cd0016b019 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -744,7 +744,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // (e.g. AMS.startActivityAsUser). final long token = Binder.clearCallingIdentity(); try { - return mService.getPackageManagerInternalLocked().resolveIntent( + return mService.getPackageManagerInternalLocked().resolveIntentExported( intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true, filterCallingUid); } finally { diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 7e62c61572d6..2ea6a3f14f64 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -467,16 +467,6 @@ public class AppTransitionController { return TRANSIT_OLD_WALLPAPER_OPEN; } - // Some devices don't show a wallpaper. In that case we should still trigger wallpaper - // transitions when animating to/from the home activity - if (wallpaperTarget == null) { - if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) { - return TRANSIT_OLD_WALLPAPER_OPEN; - } else if (topClosingApp != null && topClosingApp.isActivityTypeHome()) { - return TRANSIT_OLD_WALLPAPER_CLOSE; - } - } - final ArraySet<WindowContainer> openingWcs = getAnimationTargets( openingApps, closingApps, true /* visible */); final ArraySet<WindowContainer> closingWcs = getAnimationTargets( @@ -488,6 +478,11 @@ public class AppTransitionController { @TransitContainerType int openingType = getTransitContainerType(openingContainer); @TransitContainerType int closingType = getTransitContainerType(closingContainer); if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) { + if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) { + // If we are opening the home task, we want to play an animation as if + // the task on top is being brought to back. + return TRANSIT_OLD_TASK_TO_BACK; + } return TRANSIT_OLD_TASK_TO_FRONT; } if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) { diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 219092baface..1266db5bff98 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -357,7 +357,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume * or seamless transformation in a rotated display. */ boolean shouldFreezeInsetsPosition(WindowState w) { - return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition() + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { + // Expect a screenshot layer has covered the screen, so it is fine to let client side + // insets animation runner update the position directly. + return false; + } + return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted && isTargetToken(w.mToken); } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index d9ab971c9a78..a3f5401fd375 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -36,6 +36,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.BackNavigationInfo; import android.window.OnBackInvokedCallbackInfo; +import android.window.ScreenCapture; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; @@ -422,7 +423,7 @@ class BackNavigationController { ComponentName activityComponent) { // Check if we have a screenshot of the previous activity, indexed by its // component name. - SurfaceControl.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots + ScreenCapture.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots .get(activityComponent.flattenToString()); return backBuffer != null ? backBuffer.getHardwareBuffer() : null; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7471993545a7..28093074303d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -231,6 +231,7 @@ import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.window.DisplayWindowPolicyController; import android.window.IDisplayAreaOrganizer; +import android.window.ScreenCapture; import android.window.TransitionRequestInfo; import com.android.internal.R; @@ -1152,6 +1153,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp(); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display); + setWindowingMode(WINDOWING_MODE_FULLSCREEN); mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); } @@ -2667,16 +2669,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override - public void setWindowingMode(int windowingMode) { - // Intentionally call onRequestedOverrideConfigurationChanged() directly to change windowing - // mode and display windowing mode atomically. - mTmpConfiguration.setTo(getRequestedOverrideConfiguration()); - mTmpConfiguration.windowConfiguration.setWindowingMode(windowingMode); - mTmpConfiguration.windowConfiguration.setDisplayWindowingMode(windowingMode); - onRequestedOverrideConfigurationChanged(mTmpConfiguration); - } - - @Override void setDisplayWindowingMode(int windowingMode) { setWindowingMode(windowingMode); } @@ -4243,7 +4235,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mImeTarget; } - private SurfaceControl createImeSurface(SurfaceControl.ScreenshotHardwareBuffer b, + private SurfaceControl createImeSurface(ScreenCapture.ScreenshotHardwareBuffer b, Transaction t) { final HardwareBuffer buffer = b.getHardwareBuffer(); ProtoLog.i(WM_DEBUG_IME, "create IME snapshot for %s, buff width=%s, height=%s", @@ -4305,7 +4297,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || mImeSurface.getWidth() != dc.mInputMethodWindow.getFrame().width() || mImeSurface.getHeight() != dc.mInputMethodWindow.getFrame().height(); if (task != null && !task.isActivityTypeHomeOrRecents()) { - SurfaceControl.ScreenshotHardwareBuffer imeBuffer = renewImeSurface + ScreenCapture.ScreenshotHardwareBuffer imeBuffer = renewImeSurface ? dc.mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task) : null; if (imeBuffer != null) { @@ -4914,12 +4906,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Send invalid rect and no width and height since it will screenshot the entire display. final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); - final SurfaceControl.DisplayCaptureArgs captureArgs = - new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) + final ScreenCapture.DisplayCaptureArgs captureArgs = + new ScreenCapture.DisplayCaptureArgs.Builder(displayToken) .setUseIdentityTransform(inRotation) .build(); - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureDisplay(captureArgs); + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureDisplay(captureArgs); final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); if (bitmap == null) { Slog.w(TAG_WM, "Failed to take screenshot"); diff --git a/services/core/java/com/android/server/wm/DisplayHashController.java b/services/core/java/com/android/server/wm/DisplayHashController.java index 543d4ad6b507..1a8b43403b25 100644 --- a/services/core/java/com/android/server/wm/DisplayHashController.java +++ b/services/core/java/com/android/server/wm/DisplayHashController.java @@ -53,9 +53,9 @@ import android.service.displayhash.IDisplayHashingService; import android.util.Size; import android.util.Slog; import android.view.MagnificationSpec; -import android.view.SurfaceControl; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; +import android.window.ScreenCapture; import com.android.internal.annotations.GuardedBy; @@ -194,7 +194,7 @@ public class DisplayHashController { return true; } - void generateDisplayHash(SurfaceControl.LayerCaptureArgs.Builder args, + void generateDisplayHash(ScreenCapture.LayerCaptureArgs.Builder args, Rect boundsInWindow, String hashAlgorithm, int uid, RemoteCallback callback) { if (!allowedToGenerateHash(uid)) { sendDisplayHashError(callback, DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS); @@ -217,8 +217,8 @@ public class DisplayHashController { args.setGrayscale(displayHashParams.isGrayscaleBuffer()); - SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer = - SurfaceControl.captureLayers(args.build()); + ScreenCapture.ScreenshotHardwareBuffer screenshotHardwareBuffer = + ScreenCapture.captureLayers(args.build()); if (screenshotHardwareBuffer == null || screenshotHardwareBuffer.getHardwareBuffer() == null) { Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content"); diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 9462d4f7829d..e0644b61772a 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -150,7 +150,10 @@ class DisplayWindowSettings { final SettingsProvider.SettingsEntry overrideSettings = mSettingsProvider.getOverrideSettings(displayInfo); overrideSettings.mWindowingMode = mode; - dc.setWindowingMode(mode); + final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea(); + if (defaultTda != null) { + defaultTda.setWindowingMode(mode); + } mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings); } @@ -253,8 +256,10 @@ class DisplayWindowSettings { // Setting windowing mode first, because it may override overscan values later. final int windowingMode = getWindowingModeLocked(settings, dc); - dc.setWindowingMode(windowingMode); - + final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea(); + if (defaultTda != null) { + defaultTda.setWindowingMode(windowingMode); + } final int userRotationMode = settings.mUserRotationMode != null ? settings.mUserRotationMode : WindowManagerPolicy.USER_ROTATION_FREE; final int userRotation = settings.mUserRotation != null @@ -311,10 +316,11 @@ class DisplayWindowSettings { * changed. */ boolean updateSettingsForDisplay(DisplayContent dc) { - if (dc.getWindowingMode() != getWindowingModeLocked(dc)) { + final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea(); + if (defaultTda != null && defaultTda.getWindowingMode() != getWindowingModeLocked(dc)) { // For the time being the only thing that may change is windowing mode, so just update // that. - dc.setWindowingMode(getWindowingModeLocked(dc)); + defaultTda.setWindowingMode(getWindowingModeLocked(dc)); return true; } return false; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 317c93e63459..ea82417a2389 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -481,7 +481,7 @@ final class LetterboxUiController { } private void updateRoundedCorners(WindowState mainWindow) { - final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); + final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); if (windowSurface != null && windowSurface.isValid()) { final Transaction transaction = mActivityRecord.getSyncTransaction(); diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index 1fd66fc2c2bc..d497d8cbf9cd 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -141,15 +141,16 @@ class ResetTargetTaskHelper implements Consumer<Task>, Predicate<ActivityRecord> return false; } else { - mResultActivities.add(r); if (r.resultTo != null) { // If this activity is sending a reply to a previous activity, we can't do // anything with it now until we reach the start of the reply chain. // NOTE: that we are assuming the result is always to the previous activity, // which is almost always the case but we really shouldn't count on. + mResultActivities.add(r); return false; } else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null && mTargetTask.affinity.equals(r.taskAffinity)) { + mResultActivities.add(r); // This activity has an affinity for our task. Either remove it if we are // clearing or move it over to our task. Note that we currently punt on the case // where we are resetting a task that is not at the top but who has activities diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index fd8b614de9b7..24d4e981e9ce 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -52,6 +52,7 @@ import android.view.SurfaceControl; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; +import android.window.ScreenCapture; import com.android.internal.R; import com.android.internal.protolog.common.ProtoLog; @@ -167,7 +168,7 @@ class ScreenRotationAnimation { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer; + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer; if (isSizeChanged) { final DisplayAddress address = displayInfo.address; if (!(address instanceof DisplayAddress.Physical)) { @@ -186,22 +187,22 @@ class ScreenRotationAnimation { // the whole display to include the rounded corner overlays. setSkipScreenshotForRoundedCornerOverlays(false, t); mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays(); - final SurfaceControl.DisplayCaptureArgs captureArgs = - new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) + final ScreenCapture.DisplayCaptureArgs captureArgs = + new ScreenCapture.DisplayCaptureArgs.Builder(displayToken) .setSourceCrop(new Rect(0, 0, width, height)) .setAllowProtected(true) .setCaptureSecureLayers(true) .build(); - screenshotBuffer = SurfaceControl.captureDisplay(captureArgs); + screenshotBuffer = ScreenCapture.captureDisplay(captureArgs); } else { - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder( + ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder( displayContent.getSurfaceControl()) .setCaptureSecureLayers(true) .setAllowProtected(true) .setSourceCrop(new Rect(0, 0, width, height)) .build(); - screenshotBuffer = SurfaceControl.captureLayers(captureArgs); + screenshotBuffer = ScreenCapture.captureLayers(captureArgs); } if (screenshotBuffer == null) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index fb68fe666c0b..b9739f03bec5 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -324,7 +324,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final int callingPid = Binder.getCallingPid(); // Validate and resolve ClipDescription data before clearing the calling identity validateAndResolveDragMimeTypeExtras(data, callingUid, callingPid, mPackageName); - validateDragFlags(flags, callingUid); + validateDragFlags(flags); final long ident = Binder.clearCallingIdentity(); try { return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource, @@ -349,11 +349,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { * Validates the given drag flags. */ @VisibleForTesting - void validateDragFlags(int flags, int callingUid) { - if (callingUid == Process.SYSTEM_UID) { - throw new IllegalStateException("Need to validate before calling identify is cleared"); - } - + void validateDragFlags(int flags) { if ((flags & View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) { if (!mCanStartTasksFromRecents) { throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission"); @@ -367,9 +363,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @VisibleForTesting void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid, int callingPid, String callingPackage) { - if (callingUid == Process.SYSTEM_UID) { - throw new IllegalStateException("Need to validate before calling identify is cleared"); - } final ClipDescription desc = data != null ? data.getDescription() : null; if (desc == null) { return; diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index f3670e49f01e..94d4ddeb465c 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -45,6 +45,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.animation.Animation; import android.view.animation.Transformation; +import android.window.ScreenCapture; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -433,16 +434,16 @@ class SurfaceAnimationRunner { private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds, Rect extensionRect, int xPos, int yPos, String layerName, Transaction startTransaction) { - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(leash /* surfaceToExtend */) + ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */) .setSourceCrop(edgeBounds) .setFrameScale(1) .setPixelFormat(PixelFormat.RGBA_8888) .setChildrenOnly(true) .setAllowProtected(true) .build(); - final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = - SurfaceControl.captureLayers(captureArgs); + final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = + ScreenCapture.captureLayers(captureArgs); if (edgeBuffer == null) { // The leash we are trying to screenshot may have been removed by this point, which is diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index a7ef36b01d91..0c36d27603c8 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.util.Slog; import android.view.SurfaceControl; +import android.window.ScreenCapture; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -89,7 +90,7 @@ class SurfaceFreezer { freezeTarget = freezeTarget != null ? freezeTarget : mAnimatable.getFreezeSnapshotTarget(); if (freezeTarget != null) { - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBufferInner( + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBufferInner( freezeTarget, startBounds); final HardwareBuffer buffer = screenshotBuffer == null ? null : screenshotBuffer.getHardwareBuffer(); @@ -183,31 +184,31 @@ class SurfaceFreezer { return mLeash != null; } - private static SurfaceControl.ScreenshotHardwareBuffer createSnapshotBuffer( + private static ScreenCapture.ScreenshotHardwareBuffer createSnapshotBuffer( @NonNull SurfaceControl target, @Nullable Rect bounds) { Rect cropBounds = null; if (bounds != null) { cropBounds = new Rect(bounds); cropBounds.offsetTo(0, 0); } - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(target) + ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder(target) .setSourceCrop(cropBounds) .setCaptureSecureLayers(true) .setAllowProtected(true) .build(); - return SurfaceControl.captureLayers(captureArgs); + return ScreenCapture.captureLayers(captureArgs); } @VisibleForTesting - SurfaceControl.ScreenshotHardwareBuffer createSnapshotBufferInner( + ScreenCapture.ScreenshotHardwareBuffer createSnapshotBufferInner( SurfaceControl target, Rect bounds) { return createSnapshotBuffer(target, bounds); } @VisibleForTesting GraphicBuffer createFromHardwareBufferInner( - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer) { + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer) { return GraphicBuffer.createFromHardwareBuffer(screenshotBuffer.getHardwareBuffer()); } @@ -220,7 +221,7 @@ class SurfaceFreezer { * @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with. */ Snapshot(SurfaceControl.Transaction t, - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) { + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) { GraphicBuffer graphicBuffer = createFromHardwareBufferInner(screenshotBuffer); mSurfaceControl = mAnimatable.makeAnimationLeash() diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 4063cae42b6b..b6c14bbfd8ed 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -172,6 +172,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { */ private final boolean mCanHostHomeTask; + private final Configuration mTempConfiguration = new Configuration(); + TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name, int displayAreaFeature) { this(displayContent, service, name, displayAreaFeature, false /* createdByOrganizer */, @@ -1893,6 +1895,15 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } @Override + public void setWindowingMode(int windowingMode) { + mTempConfiguration.setTo(getRequestedOverrideConfiguration()); + WindowConfiguration tempRequestWindowConfiguration = mTempConfiguration.windowConfiguration; + tempRequestWindowConfiguration.setWindowingMode(windowingMode); + tempRequestWindowConfiguration.setDisplayWindowingMode(windowingMode); + onRequestedOverrideConfigurationChanged(mTempConfiguration); + } + + @Override TaskDisplayArea getTaskDisplayArea() { return this; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index f784f7105bfe..de4c84c27a69 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -89,6 +89,7 @@ import android.view.DisplayInfo; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; +import android.window.ScreenCapture; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizerToken; @@ -306,7 +307,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is // implemented - HashMap<String, SurfaceControl.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>(); + HashMap<String, ScreenCapture.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>(); private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper = new EnsureActivitiesVisibleHelper(this); @@ -1595,12 +1596,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { boolean pauseImmediately = false; boolean shouldAutoPip = false; if (resuming != null) { + // We do not want to trigger auto-PiP upon launch of a translucent activity. + final boolean resumingOccludesParent = resuming.occludesParent(); // Resuming the new resume activity only if the previous activity can't go into Pip // since we want to give Pip activities a chance to enter Pip before resuming the // next activity. final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( "shouldAutoPipWhilePausing", userLeaving); - if (userLeaving && lastResumedCanPip + if (userLeaving && resumingOccludesParent && lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { shouldAutoPip = true; } else if (!lastResumedCanPip) { @@ -1859,7 +1862,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s", r.mActivityComponent.flattenToString()); Rect outBounds = r.getBounds(); - SurfaceControl.ScreenshotHardwareBuffer backBuffer = SurfaceControl.captureLayers( + ScreenCapture.ScreenshotHardwareBuffer backBuffer = ScreenCapture.captureLayers( r.mSurfaceControl, new Rect(0, 0, outBounds.width(), outBounds.height()), 1f); diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 136209417f39..8444489876b8 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -135,7 +135,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { final DisplayContent display = suggestedDisplayArea.mDisplayContent; if (DEBUG) { appendLog("display-id=" + display.getDisplayId() - + " display-windowing-mode=" + display.getWindowingMode() + + " task-display-area-windowing-mode=" + suggestedDisplayArea.getWindowingMode() + " suggested-display-area=" + suggestedDisplayArea); } @@ -154,7 +154,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // source is a freeform window in a fullscreen display launching an activity on the same // display. if (launchMode == WINDOWING_MODE_UNDEFINED - && canInheritWindowingModeFromSource(display, source)) { + && canInheritWindowingModeFromSource(display, suggestedDisplayArea, source)) { // The source's windowing mode may be different from its task, e.g. activity is set // to fullscreen and its task is pinned windowing mode when the activity is entering // pip. @@ -182,7 +182,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is // different, we should recalculating the bounds. boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false; - final boolean canApplyFreeformPolicy = canApplyFreeformWindowPolicy(display, launchMode); + final boolean canApplyFreeformPolicy = + canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode); if (mSupervisor.canUseActivityOptionsLaunchBounds(options) && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) { hasInitialBounds = true; @@ -237,7 +238,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { == display.getDisplayId())) { // Only set windowing mode if display is in freeform. If the display is in fullscreen // mode we should only launch a task in fullscreen mode. - if (currentParams.hasWindowingMode() && display.inFreeformWindowingMode()) { + if (currentParams.hasWindowingMode() + && suggestedDisplayArea.inFreeformWindowingMode()) { launchMode = currentParams.mWindowingMode; fullyResolvedCurrentParam = launchMode != WINDOWING_MODE_FREEFORM; if (DEBUG) { @@ -265,11 +267,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // this step is to define the default policy when there is no initial bounds or a fully // resolved current params from callers. - // hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay is set if the outParams.mBounds + // hasInitialBoundsForSuggestedDisplayAreaInFreeformMode is set if the outParams.mBounds // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is // different, we should recalcuating the bounds. - boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = false; - if (display.inFreeformWindowingMode()) { + boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = false; + if (suggestedDisplayArea.inFreeformWindowingMode()) { if (launchMode == WINDOWING_MODE_PINNED) { if (DEBUG) appendLog("picture-in-picture"); } else if (!root.isResizeable()) { @@ -278,7 +280,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (outParams.mBounds.isEmpty()) { getTaskBounds(root, suggestedDisplayArea, layout, launchMode, hasInitialBounds, outParams.mBounds); - hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = true; + hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true; } if (DEBUG) appendLog("unresizable-freeform"); } else { @@ -288,10 +290,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } } } else { - if (DEBUG) appendLog("non-freeform-display"); + if (DEBUG) appendLog("non-freeform-task-display-area"); } // If launch mode matches display windowing mode, let it inherit from display. - outParams.mWindowingMode = launchMode == display.getWindowingMode() + outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode() ? WINDOWING_MODE_UNDEFINED : launchMode; if (phase == PHASE_WINDOWING_MODE) { @@ -301,7 +303,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // STEP 3: Finalize the display area. Here we allow WM shell route all launches that match // certain criteria to specific task display areas. final int resolvedMode = (launchMode != WINDOWING_MODE_UNDEFINED) ? launchMode - : display.getWindowingMode(); + : suggestedDisplayArea.getWindowingMode(); TaskDisplayArea taskDisplayArea = suggestedDisplayArea; // If launch task display area is set in options we should just use it. We assume the // suggestedDisplayArea has the right one in this case. @@ -319,14 +321,17 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { mTmpDisplayArea = displayArea; return true; }); - // We may need to recalculate the bounds if the new TaskDisplayArea is different from - // the suggested one we used to calculate the bounds. + // We may need to recalculate the bounds and the windowing mode if the new + // TaskDisplayArea is different from the suggested one we used to calculate the two + // configurations. if (mTmpDisplayArea != null && mTmpDisplayArea != suggestedDisplayArea) { + outParams.mWindowingMode = (launchMode == mTmpDisplayArea.getWindowingMode()) + ? WINDOWING_MODE_UNDEFINED : launchMode; if (hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow) { outParams.mBounds.setEmpty(); getLayoutBounds(mTmpDisplayArea, root, layout, outParams.mBounds); hasInitialBounds = !outParams.mBounds.isEmpty(); - } else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay) { + } else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformMode) { outParams.mBounds.setEmpty(); getTaskBounds(root, mTmpDisplayArea, layout, launchMode, hasInitialBounds, outParams.mBounds); @@ -533,7 +538,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display, - @Nullable ActivityRecord source) { + TaskDisplayArea suggestedDisplayArea, @Nullable ActivityRecord source) { if (source == null) { return false; } @@ -541,7 +546,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // There is not really any strong reason to tie the launching windowing mode and the source // on freeform displays. The launching windowing mode is more tied to the content of the new // activities. - if (display.inFreeformWindowingMode()) { + if (suggestedDisplayArea.inFreeformWindowingMode()) { return false; } @@ -557,9 +562,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return display.getDisplayId() == source.getDisplayId(); } - private boolean canApplyFreeformWindowPolicy(@NonNull DisplayContent display, int launchMode) { + private boolean canApplyFreeformWindowPolicy(@NonNull TaskDisplayArea suggestedDisplayArea, + int launchMode) { return mSupervisor.mService.mSupportsFreeformWindowManagement - && (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM); + && (suggestedDisplayArea.inFreeformWindowingMode() + || launchMode == WINDOWING_MODE_FREEFORM); } private boolean canApplyPipWindowPolicy(int launchMode) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 534616fb7207..9306749f17b9 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -58,6 +58,7 @@ import android.view.ThreadedRenderer; import android.view.WindowInsets.Type; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager.LayoutParams; +import android.window.ScreenCapture; import android.window.TaskSnapshot; import com.android.internal.R; @@ -389,11 +390,11 @@ class TaskSnapshotController { } @Nullable - SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, + ScreenCapture.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, TaskSnapshot.Builder builder) { Point taskSize = new Point(); Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createTaskSnapshot"); - final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task, + final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task, mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); builder.setTaskSize(taskSize); @@ -401,7 +402,7 @@ class TaskSnapshotController { } @Nullable - private SurfaceControl.ScreenshotHardwareBuffer createImeSnapshot(@NonNull Task task, + private ScreenCapture.ScreenshotHardwareBuffer createImeSnapshot(@NonNull Task task, int pixelFormat) { if (task.getSurfaceControl() == null) { if (DEBUG_SCREENSHOT) { @@ -410,11 +411,11 @@ class TaskSnapshotController { return null; } final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow; - SurfaceControl.ScreenshotHardwareBuffer imeBuffer = null; + ScreenCapture.ScreenshotHardwareBuffer imeBuffer = null; if (imeWindow != null && imeWindow.isWinVisibleLw()) { final Rect bounds = imeWindow.getParentFrame(); bounds.offsetTo(0, 0); - imeBuffer = SurfaceControl.captureLayersExcluding(imeWindow.getSurfaceControl(), + imeBuffer = ScreenCapture.captureLayersExcluding(imeWindow.getSurfaceControl(), bounds, 1.0f, pixelFormat, null); } return imeBuffer; @@ -425,7 +426,7 @@ class TaskSnapshotController { * task to keep IME visibility while app transitioning. */ @Nullable - SurfaceControl.ScreenshotHardwareBuffer snapshotImeFromAttachedTask(@NonNull Task task) { + ScreenCapture.ScreenshotHardwareBuffer snapshotImeFromAttachedTask(@NonNull Task task) { // Check if the IME targets task ready to take the corresponding IME snapshot, if not, // means the task is not yet visible for some reasons and no need to snapshot IME surface. if (checkIfReadyToSnapshot(task) == null) { @@ -438,7 +439,7 @@ class TaskSnapshotController { } @Nullable - SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, + ScreenCapture.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) { if (task.getSurfaceControl() == null) { if (DEBUG_SCREENSHOT) { @@ -473,8 +474,8 @@ class TaskSnapshotController { } builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible()); - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureLayersExcluding( + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureLayersExcluding( task.getSurfaceControl(), mTmpRect, scaleFraction, pixelFormat, excludeLayers); if (outTaskSize != null) { @@ -508,7 +509,7 @@ class TaskSnapshotController { return null; } - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = createTaskSnapshot(task, builder); if (screenshotBuffer == null) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 32b753241cab..723aa19bc2b5 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -40,8 +40,9 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -77,6 +78,7 @@ import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.RemoteTransition; +import android.window.ScreenCapture; import android.window.TransitionInfo; import com.android.internal.annotations.VisibleForTesting; @@ -1838,8 +1840,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_WILL_IME_SHOWN; } } + Task parentTask = null; final ActivityRecord record = wc.asActivityRecord(); if (record != null) { + parentTask = record.getTask(); if (record.mUseTransferredAnimation) { flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; } @@ -1847,6 +1851,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_IS_VOICE_INTERACTION; } } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment != null && task == null) { + parentTask = taskFragment.getTask(); + } + if (parentTask != null) { + if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { + // Whether this is in a Task with embedded activity. + flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + } + final Rect taskBounds = parentTask.getBounds(); + final Rect startBounds = mAbsoluteBounds; + final Rect endBounds = wc.getBounds(); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + // Whether the container fills the Task bounds before and after the transition. + flags |= FLAG_FILLS_TASK; + } + } final DisplayContent dc = wc.asDisplayContent(); if (dc != null) { flags |= FLAG_IS_DISPLAY; @@ -1863,9 +1887,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (occludesKeyguard(wc)) { flags |= FLAG_OCCLUDES_KEYGUARD; } - if (wc.isEmbedded()) { - flags |= FLAG_IS_EMBEDDED; - } return flags; } } @@ -2105,14 +2126,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Rect cropBounds = new Rect(bounds); cropBounds.offsetTo(0, 0); - SurfaceControl.LayerCaptureArgs captureArgs = - new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl()) + ScreenCapture.LayerCaptureArgs captureArgs = + new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl()) .setSourceCrop(cropBounds) .setCaptureSecureLayers(true) .setAllowProtected(true) .build(); - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureLayers(captureArgs); + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + ScreenCapture.captureLayers(captureArgs); final HardwareBuffer buffer = screenshotBuffer == null ? null : screenshotBuffer.getHardwareBuffer(); if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 221e18699c1c..77d0f378cee1 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -67,7 +67,7 @@ class TransitionController { /** Which sync method to use for transition syncs. */ static final int SYNC_METHOD = - android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", true) + android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", false) ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE; /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index d652b8e0ab32..0fd3e9b4abae 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -53,6 +53,7 @@ import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; +import android.window.ScreenCapture; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogImpl; @@ -931,7 +932,7 @@ class WallpaperController { final Rect bounds = wallpaperWindowState.getBounds(); bounds.offsetTo(0, 0); - SurfaceControl.ScreenshotHardwareBuffer wallpaperBuffer = SurfaceControl.captureLayers( + ScreenCapture.ScreenshotHardwareBuffer wallpaperBuffer = ScreenCapture.captureLayers( wallpaperWindowState.getSurfaceControl(), bounds, 1 /* frameScale */); if (wallpaperBuffer == null) { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index bc668526b24c..8cc0d5dd8c1b 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -622,7 +622,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< setInitialSurfaceControlProperties(makeSurface()); } - void setInitialSurfaceControlProperties(SurfaceControl.Builder b) { + void setInitialSurfaceControlProperties(Builder b) { setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build()); if (showSurfaceOnCreation()) { getSyncTransaction().show(mSurfaceControl); @@ -652,7 +652,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mLastSurfacePosition.set(0, 0); mLastDeltaRotation = Surface.ROTATION_0; - final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null) + final Builder b = mWmService.makeSurfaceBuilder(null) .setContainerLayer() .setName(getName()); @@ -2437,7 +2437,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } while (current != null); } - SurfaceControl.Builder makeSurface() { + Builder makeSurface() { final WindowContainer p = getParent(); return p.makeChildSurface(this); } @@ -2446,7 +2446,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @param child The WindowContainer this child surface is for, or null if the Surface * is not assosciated with a WindowContainer (e.g. a surface used for Dimming). */ - SurfaceControl.Builder makeChildSurface(WindowContainer child) { + Builder makeChildSurface(WindowContainer child) { final WindowContainer p = getParent(); // Give the parent a chance to set properties. In hierarchy v1 we rely // on this to set full-screen dimensions on all our Surface-less Layers. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5ad6fbd69d7e..88c47dbaaab4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -289,6 +289,7 @@ import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; import android.window.ClientWindowFrames; import android.window.ITaskFpsCallback; +import android.window.ScreenCapture; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -3980,7 +3981,7 @@ public class WindowManagerService extends IWindowManager.Stub * Generates and returns an up-to-date {@link Bitmap} for the specified taskId. * * @param taskId The task ID of the task for which a Bitmap is requested. - * @param layerCaptureArgsBuilder A {@link SurfaceControl.LayerCaptureArgs.Builder} with + * @param layerCaptureArgsBuilder A {@link ScreenCapture.LayerCaptureArgs.Builder} with * arguments for how to capture the Bitmap. The caller can * specify any arguments, but this method will ensure that the * specified task's SurfaceControl is used and the crop is set to @@ -3990,7 +3991,7 @@ public class WindowManagerService extends IWindowManager.Stub */ @Nullable public Bitmap captureTaskBitmap(int taskId, - @NonNull SurfaceControl.LayerCaptureArgs.Builder layerCaptureArgsBuilder) { + @NonNull ScreenCapture.LayerCaptureArgs.Builder layerCaptureArgsBuilder) { if (mTaskSnapshotController.shouldDisableSnapshots()) { return null; } @@ -4009,7 +4010,7 @@ public class WindowManagerService extends IWindowManager.Stub mTmpRect.offsetTo(0, 0); final SurfaceControl sc = task.getSurfaceControl(); - final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers( + final ScreenCapture.ScreenshotHardwareBuffer buffer = ScreenCapture.captureLayers( layerCaptureArgsBuilder.setLayer(sc).setSourceCrop(mTmpRect).build()); if (buffer == null) { Slog.w(TAG, "Could not get screenshot buffer for taskId: " + taskId); @@ -5810,6 +5811,21 @@ public class WindowManagerService extends IWindowManager.Stub return -1; } + /** + * Return the display Id that has the given uniqueId. Unique ID is defined in + * {@link DisplayInfo#uniqueId}. + */ + @Override + public int getDisplayIdByUniqueId(String uniqueId) { + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(uniqueId); + if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { + return displayContent.mDisplayId; + } + } + return -1; + } + @Override public void setForcedDisplayDensityForUser(int displayId, int density, int userId) { if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS) @@ -9085,8 +9101,8 @@ public class WindowManagerService extends IWindowManager.Stub // be covering it with the same uid. We want to make sure we include content that's // covering to ensure we get as close as possible to what the user sees final int uid = session.mUid; - SurfaceControl.LayerCaptureArgs.Builder args = - new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl) + ScreenCapture.LayerCaptureArgs.Builder args = + new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl) .setUid(uid) .setSourceCrop(boundsInDisplay); diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index c22091b4eacb..283830430c1a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -264,8 +264,23 @@ public class WindowManagerShellCommand extends ShellCommand { private int runDisplayDensity(PrintWriter pw) throws RemoteException { String densityStr = getNextArg(); + String option = getNextOption(); + String arg = getNextArg(); int density; - final int displayId = getDisplayId(densityStr); + int displayId = Display.DEFAULT_DISPLAY; + if ("-d".equals(option) && arg != null) { + try { + displayId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: bad number " + e); + } + } else if ("-u".equals(option) && arg != null) { + displayId = mInterface.getDisplayIdByUniqueId(arg); + if (displayId == Display.INVALID_DISPLAY) { + getErrPrintWriter().println("Error: the uniqueId is invalid "); + return -1; + } + } if (densityStr == null) { printInitialDisplayDensity(pw, displayId); @@ -1312,7 +1327,7 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" size [reset|WxH|WdpxHdp] [-d DISPLAY_ID]"); pw.println(" Return or override display size."); pw.println(" width and height in pixels unless suffixed with 'dp'."); - pw.println(" density [reset|DENSITY] [-d DISPLAY_ID]"); + pw.println(" density [reset|DENSITY] [-d DISPLAY_ID] [-u UNIQUE_ID]"); pw.println(" Return or override display density."); pw.println(" folded-area [reset|LEFT,TOP,RIGHT,BOTTOM]"); pw.println(" Return or override folded area."); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 72e7e65ae43a..805559035ef9 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; @@ -631,12 +630,6 @@ class WindowToken extends WindowContainer<WindowState> { getResolvedOverrideConfiguration().updateFrom( mFixedRotationTransformState.mRotatedOverrideConfiguration); } - if (getTaskDisplayArea() == null) { - // We only defined behaviors of system windows in fullscreen mode, i.e. windows not - // contained in a task display area. - getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode( - WINDOWING_MODE_FULLSCREEN); - } } @Override diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java index 261dda7ab15d..b93b8d866a00 100644 --- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java +++ b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java @@ -30,6 +30,7 @@ import android.media.ImageReader; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; +import android.window.ScreenCapture; import java.nio.ByteBuffer; import java.util.Arrays; @@ -118,8 +119,8 @@ public class RotationAnimationUtils { Point size = new Point(); display.getSize(size); Rect crop = new Rect(0, 0, size.x, size.y); - SurfaceControl.ScreenshotHardwareBuffer buffer = - SurfaceControl.captureLayers(surfaceControl, crop, 1); + ScreenCapture.ScreenshotHardwareBuffer buffer = + ScreenCapture.captureLayers(surfaceControl, crop, 1); if (buffer == null) { return 0; } diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp index 8f32c47fcb5a..571534f5d16e 100644 --- a/services/core/jni/gnss/Utils.cpp +++ b/services/core/jni/gnss/Utils.cpp @@ -195,6 +195,8 @@ jobject translateGnssLocation(JNIEnv* env, const android::hardware::gnss::GnssLo flags = static_cast<uint32_t>(location.elapsedRealtime.flags); if (flags & android::hardware::gnss::ElapsedRealtime::HAS_TIMESTAMP_NS) { SET(ElapsedRealtimeNanos, location.elapsedRealtime.timestampNs); + } else { + SET(ElapsedRealtimeNanos, android::elapsedRealtimeNano()); } if (flags & android::hardware::gnss::ElapsedRealtime::HAS_TIME_UNCERTAINTY_NS) { SET(ElapsedRealtimeUncertaintyNanos, diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 6b05d8f74a5e..267cff6652bb 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -97,6 +97,16 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> + <!-- Set of thresholds that dictate the change needed for screen brightness + adaptations while in idle mode --> + <xs:element type="thresholds" name="displayBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> + <!-- Set of thresholds that dictate the change needed for ambient brightness + adaptations while in idle mode --> + <xs:element type="thresholds" name="ambientBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> @@ -319,13 +329,13 @@ <!-- Thresholds for brightness changes. --> <xs:complexType name="thresholds"> <xs:sequence> - <!-- Brightening thresholds. --> + <!-- Brightening thresholds for active screen brightness mode. --> <xs:element name="brighteningThresholds" type="brightnessThresholds" minOccurs="0" maxOccurs="1" > <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> - <!-- Darkening thresholds. --> + <!-- Darkening thresholds for active screen brightness mode. --> <xs:element name="darkeningThresholds" type="brightnessThresholds" minOccurs="0" maxOccurs="1" > <xs:annotation name="nonnull"/> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index fb7a920f7d82..f8bff757f1ac 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -61,11 +61,13 @@ package com.android.server.display.config { public class DisplayConfiguration { ctor public DisplayConfiguration(); method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds(); + method public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholdsIdle(); method public final java.math.BigInteger getAmbientLightHorizonLong(); method public final java.math.BigInteger getAmbientLightHorizonShort(); method public com.android.server.display.config.AutoBrightness getAutoBrightness(); method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping(); method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds(); + method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); method public final com.android.server.display.config.SensorDetails getProxSensor(); @@ -80,11 +82,13 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling(); method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds); + method public final void setAmbientBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); method public final void setAmbientLightHorizonLong(java.math.BigInteger); method public final void setAmbientLightHorizonShort(java.math.BigInteger); method public void setAutoBrightness(com.android.server.display.config.AutoBrightness); method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping); method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds); + method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); method public final void setProxSensor(com.android.server.display.config.SensorDetails); diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 3c6866205fda..f05b1d47ac0b 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -218,6 +218,9 @@ public final class ProfcollectForwardingService extends SystemService { BackgroundThread.get().getThreadHandler().post( () -> { try { + if (sSelfService.mIProfcollect == null) { + return; + } sSelfService.mIProfcollect.process(); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to process profiles in background: " diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 9acc5b9f2214..c0688d131610 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -15,10 +15,8 @@ */ package com.android.server.appop; -import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; -import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.OP_COARSE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_READ_SMS; @@ -41,7 +39,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; -import android.app.ActivityManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; import android.content.ContentResolver; @@ -335,124 +332,6 @@ public class AppOpsServiceTest { assertThat(getLoggedOps()).isNull(); } - private void setupProcStateTests() { - // For the location proc state tests - mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_FOREGROUND); - mAppOpsService.mConstants.FG_SERVICE_STATE_SETTLE_TIME = 0; - mAppOpsService.mConstants.TOP_STATE_SETTLE_TIME = 0; - mAppOpsService.mConstants.BG_STATE_SETTLE_TIME = 0; - } - - @Test - public void testUidProcStateChange_cachedToTopToCached() throws Exception { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_cachedToFgs() { - setupProcStateTests(); - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_cachedToFgsLocation() { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_topToFgs() throws Exception { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_topToFgsLocationToFgs() throws Exception { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - private List<PackageOps> getLoggedOps() { return mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName, null /* all ops */); } diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java new file mode 100644 index 000000000000..86e126475654 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -0,0 +1,910 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_FOREGROUND; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_WIFI_SCAN; +import static android.app.AppOpsManager.UID_STATE_BACKGROUND; +import static android.app.AppOpsManager.UID_STATE_CACHED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; +import static android.app.AppOpsManager.UID_STATE_TOP; + +import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.os.Handler; +import android.os.Message; +import android.util.SparseArray; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.os.Clock; +import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.quality.Strictness; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +public class AppOpsUidStateTrackerTest { + + private static final int UID = 10001; + + // An op code that's not location/cam/mic so that we can test the code for evaluating mode + // without a specific capability associated with it. + public static final int OP_NO_CAPABILITIES = OP_WIFI_SCAN; + + @Mock + ActivityManagerInternal mAmi; + + @Mock + Handler mHandler; + + @Mock + AppOpsService.Constants mConstants; + + AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(); + + AppOpsUidStateTracker mIntf; + + StaticMockitoSession mSession; + + @Before + public void setUp() { + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L; + mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L; + mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L; + mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants); + } + + @After + public void tearDown() { + mSession.finishMocking(); + } + + /** + * This class makes the assumption that all ops are restricted at the same state, this is likely + * to be the case forever or become obsolete with the capability mechanism. If this fails + * something in {@link AppOpsUidStateTrackerImpl} might break when reporting if foreground mode + * might change. + */ + @Test + public void testConstantFirstUnrestrictedUidState() { + for (int i = 0; i < AppOpsManager.getNumOps(); i++) { + assertEquals(UID_STATE_MAX_LAST_NON_RESTRICTED, + AppOpsManager.resolveFirstUnrestrictedUidState(i)); + } + } + + @Test + public void testNoCapability() { + procStateBuilder(UID) + .topState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testForegroundWithMicrophoneCapability() { + procStateBuilder(UID) + .topState() + .microphoneCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundWithMicrophoneCapability() { + procStateBuilder(UID) + .backgroundState() + .microphoneCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testForegroundWithCameraCapability() { + procStateBuilder(UID) + .topState() + .cameraCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundWithCameraCapability() { + procStateBuilder(UID) + .backgroundState() + .cameraCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testForegroundWithLocationCapability() { + procStateBuilder(UID) + .topState() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundWithLocationCapability() { + procStateBuilder(UID) + .backgroundState() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testForegroundNotCapabilitiesTracked() { + procStateBuilder(UID) + .topState() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundNotCapabilitiesTracked() { + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundToForegroundTransition() { + procStateBuilder(UID) + .backgroundState() + .update(); + assertBackground(UID); + + procStateBuilder(UID) + .topState() + .update(); + assertForeground(UID); + } + + @Test + public void testForegroundToBackgroundTransition() { + procStateBuilder(UID) + .topState() + .update(); + assertForeground(UID); + + procStateBuilder(UID) + .backgroundState() + .update(); + // Still in foreground due to settle time + assertForeground(UID); + + AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); + AtomicLong delayAtomicReference = new AtomicLong(); + + getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference); + Message message = messageAtomicReference.get(); + long delay = delayAtomicReference.get(); + + assertNotNull(message); + assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay); + + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); + message.getCallback().run(); + assertBackground(UID); + } + + @Test + public void testForegroundServiceToBackgroundTransition() { + procStateBuilder(UID) + .foregroundServiceState() + .update(); + assertForeground(UID); + + procStateBuilder(UID) + .backgroundState() + .update(); + // Still in foreground due to settle time + assertForeground(UID); + + AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); + AtomicLong delayAtomicReference = new AtomicLong(); + + getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference); + Message message = messageAtomicReference.get(); + long delay = delayAtomicReference.get(); + + assertNotNull(message); + assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay); + + mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1); + message.getCallback().run(); + assertBackground(UID); + } + + @Test + public void testEarlyUpdateDoesntCommit() { + procStateBuilder(UID) + .foregroundServiceState() + .update(); + assertForeground(UID); + + procStateBuilder(UID) + .backgroundState() + .update(); + // Still in foreground due to settle time + assertForeground(UID); + + AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); + + getPostDelayedMessageArguments(messageAtomicReference, null); + Message message = messageAtomicReference.get(); + + // 1 ms short of settle time + mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1); + message.getCallback().run(); + assertForeground(UID); + } + + @Test + public void testMicrophoneCapabilityAdded() { + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .backgroundState() + .microphoneCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + } + + @Test + public void testMicrophoneCapabilityRemoved() { + procStateBuilder(UID) + .backgroundState() + .microphoneCapability() + .update(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + } + + @Test + public void testCameraCapabilityAdded() { + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .backgroundState() + .cameraCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testCameraCapabilityRemoved() { + procStateBuilder(UID) + .backgroundState() + .cameraCapability() + .update(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testLocationCapabilityAdded() { + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .backgroundState() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testLocationCapabilityRemoved() { + procStateBuilder(UID) + .backgroundState() + .locationCapability() + .update(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testVisibleAppWidget() { + procStateBuilder(UID) + .backgroundState() + .update(); + + SparseArray<String> appPackageNames = new SparseArray<>(); + appPackageNames.put(UID, ""); + mIntf.updateAppWidgetVisibility(appPackageNames, true); + + assertForeground(UID); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testPendingTop() { + procStateBuilder(UID) + .backgroundState() + .update(); + + doReturn(true).when(mAmi).isPendingTopUid(eq(UID)); + + assertForeground(UID); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testTempAllowlist() { + procStateBuilder(UID) + .backgroundState() + .update(); + + doReturn(true).when(mAmi).isTempAllowlistedForFgsWhileInUse(eq(UID)); + + assertForeground(UID); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testUidStateChangedCallbackNewProcessTop() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .topState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + + verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true)); + } + + @Test + public void testUidStateChangedCallbackNewProcessForegroundService() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .foregroundServiceState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + + verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true)); + } + + @Test + public void testUidStateChangedCallbackNewProcessForeground() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .foregroundState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + + verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true)); + } + + @Test + public void testUidStateChangedCallbackNewProcessBackground() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + + verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false)); + } + + @Test + public void testUidStateChangedCallbackNewProcessCached() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .cachedState() + .update(); + + // Cached is the default, no change in uid state. + verify(cb, times(0)).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testUidStateChangedCallbackCachedToBackground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, + ActivityManager.PROCESS_STATE_RECEIVER); + } + + @Test + public void testUidStateChangedCallbackCachedToForeground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, + ActivityManager.PROCESS_STATE_BOUND_TOP); + } + + @Test + public void testUidStateChangedCallbackCachedToForegroundService() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + } + + @Test + public void testUidStateChangedCallbackCachedToTop() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, + ActivityManager.PROCESS_STATE_TOP); + } + + @Test + public void testUidStateChangedCallbackBackgroundToCached() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_RECEIVER, + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); + } + + @Test + public void testUidStateChangedCallbackBackgroundToForeground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_RECEIVER, + ActivityManager.PROCESS_STATE_BOUND_TOP); + } + + @Test + public void testUidStateChangedCallbackBackgroundToForegroundService() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_RECEIVER, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + } + + @Test + public void testUidStateChangedCallbackBackgroundToTop() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_RECEIVER, + ActivityManager.PROCESS_STATE_TOP); + } + + @Test + public void testUidStateChangedCallbackForegroundToCached() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_BOUND_TOP, + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); + } + + @Test + public void testUidStateChangedCallbackForegroundToBackground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_BOUND_TOP, + ActivityManager.PROCESS_STATE_RECEIVER); + } + + @Test + public void testUidStateChangedCallbackForegroundToForegroundService() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_BOUND_TOP, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + } + + @Test + public void testUidStateChangedCallbackForegroundToTop() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_BOUND_TOP, + ActivityManager.PROCESS_STATE_TOP); + } + + @Test + public void testUidStateChangedCallbackForegroundServiceToCached() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); + } + + @Test + public void testUidStateChangedCallbackForegroundServiceToBackground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + ActivityManager.PROCESS_STATE_RECEIVER); + } + + @Test + public void testUidStateChangedCallbackForegroundServiceToForeground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + ActivityManager.PROCESS_STATE_BOUND_TOP); + } + + @Test + public void testUidStateChangedCallbackForegroundServiceToTop() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + ActivityManager.PROCESS_STATE_TOP); + } + + @Test + public void testUidStateChangedCallbackTopToCached() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_TOP, + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); + } + + @Test + public void testUidStateChangedCallbackTopToBackground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_TOP, + ActivityManager.PROCESS_STATE_RECEIVER); + } + + @Test + public void testUidStateChangedCallbackTopToForeground() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_TOP, + ActivityManager.PROCESS_STATE_BOUND_TOP); + } + + @Test + public void testUidStateChangedCallbackTopToForegroundService() { + testUidStateChangedCallback( + ActivityManager.PROCESS_STATE_TOP, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + } + + @Test + public void testUidStateChangedCallbackCachedToNonexistent() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .cachedState() + .update(); + + procStateBuilder(UID) + .nonExistentState() + .update(); + + verify(mHandler, never()).post(any()); + verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testUidStateChangedCallbackBackgroundToNonexistent() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .nonExistentState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false)); + } + + @Test + public void testUidStateChangedCallbackForegroundToNonexistent() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .foregroundState() + .update(); + + procStateBuilder(UID) + .nonExistentState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); + } + + @Test + public void testUidStateChangedCallbackForegroundServiceToNonexistent() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .foregroundServiceState() + .update(); + + procStateBuilder(UID) + .nonExistentState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); + } + + @Test + public void testUidStateChangedCallbackTopToNonexistent() { + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .topState() + .update(); + + procStateBuilder(UID) + .nonExistentState() + .update(); + + getLatestPostMessageArgument().getCallback().run(); + verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); + } + + public void testUidStateChangedCallback(int initialState, int finalState) { + int initialUidState = processStateToUidState(initialState); + int finalUidState = processStateToUidState(finalState); + boolean foregroundChange = initialUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != finalUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED; + boolean finalUidStateIsBackgroundAndLessImportant = + finalUidState > UID_STATE_MAX_LAST_NON_RESTRICTED + && finalUidState > initialUidState; + + UidStateChangedCallback cb = addUidStateChangeCallback(); + + procStateBuilder(UID) + .setState(initialState) + .update(); + + procStateBuilder(UID) + .setState(finalState) + .update(); + + if (finalUidStateIsBackgroundAndLessImportant) { + AtomicReference<Message> delayedMessage = new AtomicReference<>(); + getPostDelayedMessageArguments(delayedMessage, new AtomicLong()); + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); + delayedMessage.get().getCallback().run(); + } + + getLatestPostMessageArgument().getCallback().run(); + verify(cb, atLeastOnce()) + .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange)); + } + + private UidStateChangedCallback addUidStateChangeCallback() { + UidStateChangedCallback cb = + Mockito.mock(UidStateChangedCallback.class); + mIntf.addUidStateChangedCallback(mHandler, cb); + return cb; + } + + /* If testForegroundNotCapabilitiesTracked fails, this assertion is probably incorrect */ + private void assertForeground(int uid) { + assertEquals(MODE_ALLOWED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + /* If testBackgroundNotCapabilitiesTracked fails, this assertion is probably incorrect */ + private void assertBackground(int uid) { + assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + private void getPostDelayedMessageArguments(AtomicReference<Message> message, + AtomicLong delay) { + + ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class); + + verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture()); + + if (message != null) { + message.set(messageCaptor.getValue()); + } + if (delay != null) { + delay.set(delayCaptor.getValue()); + } + } + + private Message getLatestPostMessageArgument() { + ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + + verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture()); + + return messageCaptor.getValue(); + } + + private UidProcStateUpdateBuilder procStateBuilder(int uid) { + return new UidProcStateUpdateBuilder(mIntf, uid); + } + + private static class UidProcStateUpdateBuilder { + private AppOpsUidStateTracker mIntf; + private int mUid; + private int mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT; + private int mCapability = 0; + + private UidProcStateUpdateBuilder(AppOpsUidStateTracker intf, int uid) { + mUid = uid; + mIntf = intf; + } + + public void update() { + mIntf.updateUidProcState(mUid, mProcState, mCapability); + } + + public UidProcStateUpdateBuilder persistentState() { + mProcState = ActivityManager.PROCESS_STATE_PERSISTENT; + return this; + } + + public UidProcStateUpdateBuilder setState(int procState) { + mProcState = procState; + return this; + } + + public UidProcStateUpdateBuilder topState() { + mProcState = ActivityManager.PROCESS_STATE_TOP; + return this; + } + + public UidProcStateUpdateBuilder foregroundServiceState() { + mProcState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return this; + } + + public UidProcStateUpdateBuilder foregroundState() { + mProcState = ActivityManager.PROCESS_STATE_BOUND_TOP; + return this; + } + + public UidProcStateUpdateBuilder backgroundState() { + mProcState = ActivityManager.PROCESS_STATE_SERVICE; + return this; + } + + public UidProcStateUpdateBuilder cachedState() { + mProcState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; + return this; + } + + public UidProcStateUpdateBuilder nonExistentState() { + mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT; + return this; + } + + public UidProcStateUpdateBuilder locationCapability() { + mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; + return this; + } + + public UidProcStateUpdateBuilder cameraCapability() { + mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; + return this; + } + + public UidProcStateUpdateBuilder microphoneCapability() { + mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + return this; + } + } + + private static class AppOpsUidStateTrackerTestClock extends Clock { + + long mElapsedRealTime = 0x5f3759df; + + @Override + public long elapsedRealtime() { + return mElapsedRealTime; + } + + void advanceTime(long time) { + mElapsedRealTime += time; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 6a18dc181393..9a4bb22d5195 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -233,4 +233,4 @@ public final class DisplayPowerControllerTest { }); when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); } -} +}
\ No newline at end of file diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java index 574bab2d9962..245b4dcb25cb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java @@ -163,6 +163,23 @@ public final class UserManagerInternalTest extends UserManagerServiceOrInternalT } @Test + public void testAssignUserToDisplay_userAlreadyAssigned() { + enableUsersOnSecondaryDisplays(); + + mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + + IllegalStateException e = assertThrows(IllegalStateException.class, + () -> mUmi.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID)); + + Log.v(TAG, "Exception: " + e); + assertWithMessage("exception (%s) message", e).that(e).hasMessageThat() + .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*" + + SECONDARY_DISPLAY_ID + ".*"); + + assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + } + + @Test public void testAssignUserToDisplay_profileOnSameDisplayAsParent() { enableUsersOnSecondaryDisplays(); addDefaultProfileAndParent(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java index 538adb211fff..6f0efb08af48 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java @@ -342,7 +342,6 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); startDefaultProfile(); - setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED); assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY) .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue(); @@ -584,11 +583,19 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC } protected final void startDefaultProfile() { - setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED); + startUser(PROFILE_USER_ID); } protected final void stopDefaultProfile() { - setUserState(PROFILE_USER_ID, UserState.STATE_STOPPING); + stopUser(PROFILE_USER_ID); + } + + protected final void startUser(@UserIdInt int userId) { + setUserState(userId, UserState.STATE_RUNNING_UNLOCKED); + } + + protected final void stopUser(@UserIdInt int userId) { + setUserState(userId, UserState.STATE_STOPPING); } // NOTE: should only called by tests that indirectly needs to check user assignments (like diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 8388a70a87e5..8b5921cd45bd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -88,6 +88,42 @@ public final class UserManagerServiceTest extends UserManagerServiceOrInternalTe .that(mUms.isCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID)).isFalse(); } + @Test + public void testIsUserRunning_StartedUserShouldReturnTrue() { + addUser(USER_ID); + startUser(USER_ID); + + assertWithMessage("isUserRunning(%s)", USER_ID) + .that(mUms.isUserRunning(USER_ID)).isTrue(); + } + + @Test + public void testIsUserRunning_StoppedUserShouldReturnFalse() { + addUser(USER_ID); + stopUser(USER_ID); + + assertWithMessage("isUserRunning(%s)", USER_ID) + .that(mUms.isUserRunning(USER_ID)).isFalse(); + } + + @Test + public void testIsUserRunning_CurrentUserStartedWorkProfileShouldReturnTrue() { + addDefaultProfileAndParent(); + startDefaultProfile(); + + assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID) + .that(mUms.isUserRunning(PROFILE_USER_ID)).isTrue(); + } + + @Test + public void testIsUserRunning_CurrentUserStoppedWorkProfileShouldReturnFalse() { + addDefaultProfileAndParent(); + stopDefaultProfile(); + + assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID) + .that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse(); + } + @Override protected boolean isUserVisible(int userId) { return mUms.isUserVisibleUnchecked(userId); diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java new file mode 100644 index 000000000000..29bddfc32ff7 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.tare; + +import static org.junit.Assert.assertEquals; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class EconomicPolicyTest { + + @Test + public void testMasksDisjoint() { + assertEquals(-1, + (-1 & EconomicPolicy.MASK_TYPE) + + (-1 & EconomicPolicy.MASK_POLICY) + + (-1 & EconomicPolicy.MASK_EVENT)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java index 849e6730ac11..00d7541a79dc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java @@ -155,6 +155,27 @@ public class AlarmQueueTest { anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any()); } + @Test + public void testAddingLargeAlarmTimes() { + final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); + final long nowElapsed = mInjector.getElapsedRealtime(); + + InOrder inOrder = inOrder(mAlarmManager); + + alarmQueue.addAlarm("com.android.test.1", Long.MAX_VALUE - 5); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .setExact(anyInt(), eq(Long.MAX_VALUE - 5), eq(ALARM_TAG), any(), any()); + alarmQueue.addAlarm("com.android.test.2", Long.MAX_VALUE - 4); + inOrder.verify(mAlarmManager, never()) + .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); + alarmQueue.addAlarm("com.android.test.3", nowElapsed + 5); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .setExact(anyInt(), eq(nowElapsed + 5), eq(ALARM_TAG), any(), any()); + alarmQueue.addAlarm("com.android.test.4", nowElapsed + 6); + inOrder.verify(mAlarmManager, never()) + .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any()); + } + /** * Verify that updating the alarm time for a key will result in the AlarmManager alarm changing, * if needed. diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java index 3e8cef9afc15..ae25c1bb3db8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java @@ -103,6 +103,24 @@ public final class SlogfTest { } @Test + public void testV_msgFormattedWithException_enabled() { + enableLogging(Log.VERBOSE); + + Slogf.v(TAG, mException, "msg in a %s", "bottle"); + + verify(()-> Slog.v(TAG, "msg in a bottle", mException)); + } + + @Test + public void testV_msgFormattedWithException_disabled() { + disableLogging(Log.VERBOSE); + + Slogf.v(TAG, "msg in a %s", "bottle"); + + verify(()-> Slog.v(eq(TAG), any(String.class), any(Throwable.class)), never()); + } + + @Test public void testD_msg() { Slogf.d(TAG, "msg"); @@ -135,6 +153,24 @@ public final class SlogfTest { } @Test + public void testD_msgFormattedWithException_enabled() { + enableLogging(Log.DEBUG); + + Slogf.d(TAG, mException, "msg in a %s", "bottle"); + + verify(()-> Slog.d(TAG, "msg in a bottle", mException)); + } + + @Test + public void testD_msgFormattedWithException_disabled() { + disableLogging(Log.DEBUG); + + Slogf.d(TAG, mException, "msg in a %s", "bottle"); + + verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never()); + } + + @Test public void testI_msg() { Slogf.i(TAG, "msg"); @@ -167,6 +203,24 @@ public final class SlogfTest { } @Test + public void testI_msgFormattedWithException_enabled() { + enableLogging(Log.INFO); + + Slogf.i(TAG, mException, "msg in a %s", "bottle"); + + verify(()-> Slog.i(TAG, "msg in a bottle", mException)); + } + + @Test + public void testI_msgFormattedWithException_disabled() { + disableLogging(Log.INFO); + + Slogf.i(TAG, mException, "msg in a %s", "bottle"); + + verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never()); + } + + @Test public void testW_msg() { Slogf.w(TAG, "msg"); @@ -218,7 +272,7 @@ public final class SlogfTest { public void testW_msgFormattedWithException_disabled() { disableLogging(Log.WARN); - Slogf.w(TAG, "msg in a %s", "bottle"); + Slogf.w(TAG, mException, "msg in a %s", "bottle"); verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never()); } @@ -268,7 +322,7 @@ public final class SlogfTest { public void testE_msgFormattedWithException_disabled() { disableLogging(Log.ERROR); - Slogf.e(TAG, "msg in a %s", "bottle"); + Slogf.e(TAG, mException, "msg in a %s", "bottle"); verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never()); } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index fe079f423094..81f899c7d645 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -181,6 +181,11 @@ public class UserControllerTest { mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. mUserController = new UserController(mInjector); + + // TODO(b/232452368): need to explicitly call setAllowUserUnlocking(), otherwise most + // tests would fail. But we might need to disable it for the onBootComplete() test (i.e, + // to make sure the users are unlocked at the right time) + mUserController.setAllowUserUnlocking(true); setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS); setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null); }); @@ -628,6 +633,16 @@ public class UserControllerTest { } @Test + public void testUserNotUnlockedBeforeAllowed() throws Exception { + mUserController.setAllowUserUnlocking(false); + + mUserController.startUser(TEST_USER_ID, /* foreground= */ false); + + verify(mInjector.mStorageManagerMock, never()) + .unlockUserKey(eq(TEST_USER_ID), anyInt(), any()); + } + + @Test public void testStartProfile_fullUserFails() { setUpUser(TEST_USER_ID1, 0); assertThrows(IllegalArgumentException.class, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index 92e1f27ab624..837b55397416 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -253,6 +255,16 @@ public class FingerprintEnrollClientTest { showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0)); } + @Test + public void testPowerPressForwardsAcquireMessage() throws RemoteException { + final FingerprintEnrollClient client = createClient(); + client.start(mCallback); + client.onPowerPressed(); + + verify(mClientMonitorCallbackConverter).onAcquired(anyInt(), + eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt()); + } + private void showHideOverlay(Consumer<FingerprintEnrollClient> block) throws RemoteException { final FingerprintEnrollClient client = createClient(); diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 9d82f1a90c77..8280fc6c962f 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -82,6 +82,8 @@ public class AutomaticBrightnessControllerTest { @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy; @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; + @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle; + @Mock HysteresisLevels mScreenBrightnessThresholdsIdle; @Mock Handler mNoOpHandler; @Mock HighBrightnessModeController mHbmController; @Mock BrightnessThrottler mBrightnessThrottler; @@ -129,6 +131,7 @@ public class AutomaticBrightnessControllerTest { INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, + mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle, mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG ); @@ -314,8 +317,9 @@ public class AutomaticBrightnessControllerTest { // Now let's do the same for idle mode mController.switchToIdleMode(); - // Called once for init, and once when switching - verify(mBrightnessMappingStrategy, times(2)).isForIdleMode(); + // Called once for init, and once when switching, + // setAmbientLux() is called twice and once in updateAutoBrightness() + verify(mBrightnessMappingStrategy, times(5)).isForIdleMode(); // Ensure, after switching, original BMS is not used anymore verifyNoMoreInteractions(mBrightnessMappingStrategy); diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ConcurrentLinkedEvictingDequeTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ConcurrentLinkedEvictingDequeTest.java new file mode 100644 index 000000000000..9169a0df4a64 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ConcurrentLinkedEvictingDequeTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.contexthub; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class ConcurrentLinkedEvictingDequeTest { + @Test + public void testMaxSize() { + int maxSize = 20; + ConcurrentLinkedEvictingDeque<Integer> deque = + new ConcurrentLinkedEvictingDeque<Integer>(maxSize); + + // add items to the queue + for (int i = 0; i < maxSize; i++) { + deque.add(i); + } + + // test the max size + deque.add(maxSize); + assertThat(deque.peek() == 0).isFalse(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java new file mode 100644 index 000000000000..8863d2796acf --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.contexthub; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.location.NanoAppMessage; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class ContextHubEventLoggerTest { + private static final ContextHubEventLogger sInstance = ContextHubEventLogger.getInstance(); + + @Test + public void testLogNanoappLoad() { + ContextHubEventLogger.NanoappLoadEvent[] events = + new ContextHubEventLogger.NanoappLoadEvent[] { + new ContextHubEventLogger.NanoappLoadEvent(0, -1, 42, -34, 100, false), + new ContextHubEventLogger.NanoappLoadEvent(0, 0, 123, 321, 001, true) + }; + String[] eventStrings = generateEventDumpStrings(events); + + // log events and test sInstance.toString() contains event details + sInstance.clear(); + sInstance.logNanoappLoad(-1, 42, -34, 100, false); + sInstance.logNanoappLoad(0, 123, 321, 001, true); + String sInstanceDump = sInstance.toString(); + for (String eventString: eventStrings) { + assertThat(eventString.length() > 0).isTrue(); + assertThat(sInstanceDump.contains(eventString)).isTrue(); + } + } + + @Test + public void testLogNanoappUnload() { + ContextHubEventLogger.NanoappUnloadEvent[] events = + new ContextHubEventLogger.NanoappUnloadEvent[] { + new ContextHubEventLogger.NanoappUnloadEvent(0, -1, 47, false), + new ContextHubEventLogger.NanoappUnloadEvent(0, 1, 0xFFFFFFFF, true) + }; + String[] eventStrings = generateEventDumpStrings(events); + + // log events and test sInstance.toString() contains event details + sInstance.clear(); + sInstance.logNanoappUnload(-1, 47, false); + sInstance.logNanoappUnload(1, 0xFFFFFFFF, true); + String sInstanceDump = sInstance.toString(); + for (String eventString: eventStrings) { + assertThat(eventString.length() > 0).isTrue(); + assertThat(sInstanceDump.contains(eventString)).isTrue(); + } + } + + @Test + public void testLogMessageFromNanoapp() { + NanoAppMessage message1 = NanoAppMessage.createMessageFromNanoApp(1, 0, + new byte[] {0x00, 0x11, 0x22, 0x33}, false); + NanoAppMessage message2 = NanoAppMessage.createMessageFromNanoApp(0, 1, + new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}, true); + ContextHubEventLogger.NanoappMessageEvent[] events = + new ContextHubEventLogger.NanoappMessageEvent[] { + new ContextHubEventLogger.NanoappMessageEvent(8, -123, message1, false), + new ContextHubEventLogger.NanoappMessageEvent(9, 321, message2, true) + }; + String[] eventStrings = generateEventDumpStrings(events); + + // log events and test sInstance.toString() contains event details + sInstance.clear(); + sInstance.logMessageFromNanoapp(-123, message1, false); + sInstance.logMessageFromNanoapp(321, message2, true); + String sInstanceDump = sInstance.toString(); + for (String eventString: eventStrings) { + assertThat(eventString.length() > 0).isTrue(); + assertThat(sInstanceDump.contains(eventString)).isTrue(); + } + } + + @Test + public void testLogMessageToNanoapp() { + NanoAppMessage message1 = NanoAppMessage.createMessageToNanoApp(1, 0, + new byte[] {0x00, 0x11, 0x22, 0x33}); + NanoAppMessage message2 = NanoAppMessage.createMessageToNanoApp(0, 1, + new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}); + ContextHubEventLogger.NanoappMessageEvent[] events = + new ContextHubEventLogger.NanoappMessageEvent[] { + new ContextHubEventLogger.NanoappMessageEvent(23, 888, message1, true), + new ContextHubEventLogger.NanoappMessageEvent(34, 999, message2, false) + }; + String[] eventStrings = generateEventDumpStrings(events); + + // log events and test sInstance.toString() contains event details + sInstance.clear(); + sInstance.logMessageToNanoapp(888, message1, true); + sInstance.logMessageToNanoapp(999, message2, false); + String sInstanceDump = sInstance.toString(); + for (String eventString: eventStrings) { + assertThat(eventString.length() > 0).isTrue(); + assertThat(sInstanceDump.contains(eventString)).isTrue(); + } + } + + @Test + public void testLogContextHubRestart() { + ContextHubEventLogger.ContextHubRestartEvent[] events = + new ContextHubEventLogger.ContextHubRestartEvent[] { + new ContextHubEventLogger.ContextHubRestartEvent(0, 1), + new ContextHubEventLogger.ContextHubRestartEvent(1, 2) + }; + String[] eventStrings = generateEventDumpStrings(events); + + // log events and test sInstance.toString() contains event details + sInstance.clear(); + sInstance.logContextHubRestart(1); + sInstance.logContextHubRestart(2); + String sInstanceDump = sInstance.toString(); + for (String eventString: eventStrings) { + assertThat(eventString.length() > 0).isTrue(); + assertThat(sInstanceDump.contains(eventString)).isTrue(); + } + } + + /** + * Generates the part of the event's toString() method that should be contained in the dump + * output (everything without the timestamp). + * + * @param events the events + * @return the string representation of the events + */ + private String[] generateEventDumpStrings(ContextHubEventLogger.ContextHubEventBase[] events) { + return (String[]) Arrays.stream(events) + .map(event -> event.toString().split(event.getClass().getSimpleName(), 2)[1]) + .toArray(String[]::new); + } +} diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index d5b923a81e4a..4100ff1ea977 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -148,6 +148,25 @@ public class BatteryStatsHistoryTest { } @Test + public void testAtraceInstantEvent() { + mHistory.forceRecordAllHistory(); + + InOrder inOrder = Mockito.inOrder(mTracer); + Mockito.when(mTracer.tracingEnabled()).thenReturn(true); + + mHistory.recordEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + HistoryItem.EVENT_WAKEUP_AP, "", 1234); + mHistory.recordEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + HistoryItem.EVENT_JOB_START, "jobname", 2468); + mHistory.recordEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + HistoryItem.EVENT_JOB_FINISH, "jobname", 2468); + + inOrder.verify(mTracer).traceInstantEvent("battery_stats.wakeupap", "wakeupap=1234:\"\""); + inOrder.verify(mTracer).traceInstantEvent("battery_stats.job", "+job=2468:\"jobname\""); + inOrder.verify(mTracer).traceInstantEvent("battery_stats.job", "-job=2468:\"jobname\""); + } + + @Test public void testConstruct() { createActiveFile(mHistory); verifyFileNumbers(mHistory, Arrays.asList(0)); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index 99a12c2c4ff3..508e7b0f5918 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -161,13 +161,8 @@ public class VibrationSettingsTest { return null; }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any()); - LocalServices.removeServiceForTest(PowerManagerInternal.class); - LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); - LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); - LocalServices.addService(VirtualDeviceManagerInternal.class, - mVirtualDeviceManagerInternalMock); + removeServicesForTest(); + addServicesForTest(); setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM); mAudioManager = mContextSpy.getSystemService(AudioManager.class); @@ -185,9 +180,34 @@ public class VibrationSettingsTest { mVibrationSettings.onSystemReady(); } + private void removeServicesForTest() { + LocalServices.removeServiceForTest(PowerManagerInternal.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + } + + private void addServicesForTest() { + LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); + LocalServices.addService(VirtualDeviceManagerInternal.class, + mVirtualDeviceManagerInternalMock); + } + @After public void tearDown() throws Exception { - LocalServices.removeServiceForTest(PowerManagerInternal.class); + removeServicesForTest(); + } + + @Test + public void create_withOnlyRequiredSystemServices() { + // The only core services that we depend on are PowerManager and PackageManager + removeServicesForTest(); + LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); + + VibrationSettings minimalVibrationSettings = new VibrationSettings(mContextSpy, + new Handler(mTestLooper.getLooper()), mVibrationConfigMock); + minimalVibrationSettings.onSystemReady(); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 49879efe4b51..798604306b43 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1704,8 +1704,8 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - public void testInfoIsPermittedForProfile_notAllowed() { - when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(false); + public void testInfoIsPermittedForProfile_notProfile() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(false); IInterface service = mock(IInterface.class); when(service.asBinder()).thenReturn(mock(IBinder.class)); @@ -1714,12 +1714,12 @@ public class ManagedServicesTest extends UiServiceTestCase { services.registerSystemService(service, null, 10, 1000); ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); - assertFalse(info.isPermittedForProfile(0)); + assertTrue(info.isPermittedForProfile(0)); } @Test - public void testInfoIsPermittedForProfile_allows() { - when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(true); + public void testInfoIsPermittedForProfile_profileAndDpmAllows() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true); when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true); IInterface service = mock(IInterface.class); @@ -1734,6 +1734,22 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testInfoIsPermittedForProfile_profileAndDpmDenies() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true); + when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(false); + + IInterface service = mock(IInterface.class); + when(service.asBinder()).thenReturn(mock(IBinder.class)); + ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); + services.registerSystemService(service, null, 10, 1000); + ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); + info.component = new ComponentName("a","b"); + + assertFalse(info.isPermittedForProfile(0)); + } + + @Test public void testUserProfiles_canProfileUseBoundServices_managedProfile() { List<UserInfo> users = new ArrayList<>(); UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0); @@ -1750,9 +1766,9 @@ public class ManagedServicesTest extends UiServiceTestCase { ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles(); profiles.updateCache(mContext); - assertTrue(profiles.canProfileUseBoundServices(ActivityManager.getCurrentUser())); - assertFalse(profiles.canProfileUseBoundServices(12)); - assertFalse(profiles.canProfileUseBoundServices(13)); + assertFalse(profiles.isProfileUser(ActivityManager.getCurrentUser())); + assertTrue(profiles.isProfileUser(12)); + assertTrue(profiles.isProfileUser(13)); } private void resetComponentsAndPackages() { 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 68551d9813d3..1e945776cf40 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -45,19 +45,16 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.ComponentName; -import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.pm.VersionedPackage; import android.os.Bundle; -import android.os.IBinder; -import android.os.IInterface; import android.os.UserHandle; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; -import android.service.notification.NotificationStats; import android.service.notification.NotificationRankingUpdate; +import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.testing.TestableContext; import android.util.ArraySet; @@ -408,64 +405,113 @@ public class NotificationListenersTest extends UiServiceTestCase { @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(); + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationRecord old0 = mock(NotificationRecord.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationRecord old1 = mock(NotificationRecord.class); + UserHandle uh1 = mock(UserHandle.class); + + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(old0); + reset(r1); + reset(old1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, never()).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); } @Test public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { - NotificationRecord r = mock(NotificationRecord.class); - NotificationStats rs = mock(NotificationStats.class); + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationStats rs0 = mock(NotificationStats.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationStats rs1 = mock(NotificationStats.class); + UserHandle uh1 = mock(UserHandle.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(); + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(rs0); + reset(r1); + reset(rs1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, never()).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); } @Test 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 cae8fd947800..92761427184b 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -174,6 +174,7 @@ import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.ZenPolicy; @@ -9837,10 +9838,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertTrue(mStrongAuthTracker.isInLockDownMode()); - mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertFalse(mStrongAuthTracker.isInLockDownMode()); + assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); } @Test @@ -9856,8 +9857,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // when entering the lockdown mode, cancel the 2 notifications. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertTrue(mStrongAuthTracker.isInLockDownMode()); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); @@ -9866,10 +9867,46 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // exit lockdown mode. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); - mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); // the notifyPostedLocked function is called twice. - verify(mListeners, times(2)).notifyPostedLocked(any(), any()); + verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); + //verify(mListeners, times(2)).notifyPostedLocked(any(), any()); + } + + @Test + public void testMakeRankingUpdateLockedInLockDownMode() { + // post 2 notifications from a same package + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 1), mTestNotificationChannel); + mService.addNotification(pkgB); + + mService.setIsVisibleToListenerReturnValue(true); + NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); + + // when only user 0 entering the lockdown mode, its notification will be suppressed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(1, nru.getRankingMap().getOrderedKeys().length); + + // User 0 exits lockdown mode. Its notification will be resumed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } @Test 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 b49e5cbfa9dc..8cf74fbf88b7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -19,10 +19,12 @@ package com.android.server.notification; import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; +import android.service.notification.StatusBarNotification; import androidx.annotation.Nullable; import com.android.internal.logging.InstanceIdSequence; +import com.android.server.notification.ManagedServices.ManagedServiceInfo; import java.util.HashSet; import java.util.Set; @@ -37,6 +39,9 @@ public class TestableNotificationManagerService extends NotificationManagerServi @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; + @Nullable + Boolean mIsVisibleToListenerReturnValue = null; + TestableNotificationManagerService(Context context, NotificationRecordLogger logger, InstanceIdSequence notificationInstanceIdSequence) { super(context, logger, notificationInstanceIdSequence); @@ -119,6 +124,19 @@ public class TestableNotificationManagerService extends NotificationManagerServi mShowReviewPermissionsNotification = setting; } + protected void setIsVisibleToListenerReturnValue(boolean value) { + mIsVisibleToListenerReturnValue = value; + } + + @Override + boolean isVisibleToListener(StatusBarNotification sbn, int notificationType, + ManagedServiceInfo listener) { + if (mIsVisibleToListenerReturnValue != null) { + return mIsVisibleToListenerReturnValue; + } + return super.isVisibleToListener(sbn, notificationType, listener); + } + public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 85e5bfd84f9f..2b0e76cbc9bd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -379,6 +379,8 @@ public class ActivityStarterTests extends WindowTestsBase { doReturn(false).when(mMockPackageManager).isInstantAppInstallerComponent(any()); doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyLong(), anyLong(), anyInt(), anyBoolean(), anyInt()); + doReturn(null).when(mMockPackageManager).resolveIntentExported(any(), any(), + anyLong(), anyLong(), anyInt(), anyBoolean(), anyInt()); doReturn(new ComponentName("", "")).when(mMockPackageManager).getSystemUiServiceComponent(); // Never review permissions diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index b5764f54ff92..4d71b30d71e2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -134,9 +134,9 @@ public class AppChangeTransitionTests extends WindowTestsBase { @Test public void testNoChangeOnOldDisplayWhenMoveDisplay() { - mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mDisplayContent.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN); final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); - dc1.setWindowingMode(WINDOWING_MODE_FREEFORM); + dc1.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FREEFORM); setUpOnDisplay(dc1); assertEquals(WINDOWING_MODE_FREEFORM, mTask.getWindowingMode()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 7244d9492deb..11ae5d4abaf8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -139,6 +139,7 @@ import android.view.View; import android.view.WindowManager; import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; +import android.window.ScreenCapture; import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; @@ -1067,6 +1068,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); + dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN); final int newOrientation = getRotatedOrientation(dc); final Task task = new TaskBuilder(mSupervisor) @@ -1126,6 +1128,7 @@ public class DisplayContentTests extends WindowTestsBase { IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); dc.getDisplayRotation().setUserRotation( WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_0); + dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN); final int newOrientation = getRotatedOrientation(dc); final Task task = new TaskBuilder(mSupervisor) @@ -2032,23 +2035,6 @@ public class DisplayContentTests extends WindowTestsBase { } @Test - public void testSetWindowingModeAtomicallyUpdatesWindoingModeAndDisplayWindowingMode() { - final DisplayContent dc = createNewDisplay(); - final Task rootTask = new TaskBuilder(mSupervisor) - .setDisplay(dc) - .build(); - doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - final Configuration config = ((Configuration) args[0]); - assertEquals(config.windowConfiguration.getWindowingMode(), - config.windowConfiguration.getDisplayWindowingMode()); - return null; - }).when(rootTask).onConfigurationChanged(any()); - dc.setWindowingMode(WINDOWING_MODE_FREEFORM); - dc.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - } - - @Test public void testForceDesktopMode() { mWm.mForceDesktopModeOnExternalDisplays = true; // Not applicable for default display @@ -2124,8 +2110,8 @@ public class DisplayContentTests extends WindowTestsBase { // Preparation: Simulate snapshot IME surface. spyOn(mWm.mTaskSnapshotController); - SurfaceControl.ScreenshotHardwareBuffer mockHwBuffer = mock( - SurfaceControl.ScreenshotHardwareBuffer.class); + ScreenCapture.ScreenshotHardwareBuffer mockHwBuffer = mock( + ScreenCapture.ScreenshotHardwareBuffer.class); doReturn(mock(HardwareBuffer.class)).when(mockHwBuffer).getHardwareBuffer(); doReturn(mockHwBuffer).when(mWm.mTaskSnapshotController).snapshotImeFromAttachedTask(any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java index 093be82c6128..c398a0a26016 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java @@ -110,7 +110,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay); assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - mPrimaryDisplay.getWindowingMode()); + mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -120,7 +120,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay); assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - mPrimaryDisplay.getWindowingMode()); + mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -131,7 +131,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay); assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - mPrimaryDisplay.getWindowingMode()); + mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -141,7 +141,8 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay); - assertEquals(WINDOWING_MODE_FREEFORM, mPrimaryDisplay.getWindowingMode()); + assertEquals(WINDOWING_MODE_FREEFORM, + mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -154,7 +155,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.updateSettingsForDisplay(mPrimaryDisplay); assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, - mPrimaryDisplay.getWindowingMode()); + mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -162,7 +163,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay); assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - mSecondaryDisplay.getWindowingMode()); + mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -172,7 +173,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay); assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - mSecondaryDisplay.getWindowingMode()); + mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -183,7 +184,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay); assertEquals(WINDOWING_MODE_FREEFORM, - mSecondaryDisplay.getWindowingMode()); + mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test @@ -194,7 +195,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay); assertEquals(WINDOWING_MODE_FREEFORM, - mSecondaryDisplay.getWindowingMode()); + mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 28fc352e2f74..4526d18d63d8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -467,8 +467,7 @@ public class DragDropControllerTests extends WindowTestsBase { public void onAnimatorScaleChanged(float scale) {} }); try { - session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION, - TEST_UID); + session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION); fail("Expected failure without permission"); } catch (SecurityException e) { // Expected failure @@ -484,8 +483,7 @@ public class DragDropControllerTests extends WindowTestsBase { public void onAnimatorScaleChanged(float scale) {} }); try { - session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION, - TEST_UID); + session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION); // Expected pass } catch (SecurityException e) { fail("Expected no failure with permission"); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 9e658e09b8b8..4f03f54bab37 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -108,9 +108,10 @@ public class RootWindowContainerTests extends WindowTestsBase { } @Test - public void testUpdateDefaultDisplayWindowingModeOnSettingsRetrieved() { + public void testUpdateDefaultTaskDisplayAreaWindowingModeOnSettingsRetrieved() { assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - mWm.getDefaultDisplayContentLocked().getWindowingMode()); + mWm.getDefaultDisplayContentLocked().getDefaultTaskDisplayArea() + .getWindowingMode()); mWm.mIsPc = true; mWm.mAtmService.mSupportsFreeformWindowManagement = true; @@ -118,7 +119,8 @@ public class RootWindowContainerTests extends WindowTestsBase { mWm.mRoot.onSettingsRetrieved(); assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, - mWm.getDefaultDisplayContentLocked().getWindowingMode()); + mWm.getDefaultDisplayContentLocked().getDefaultTaskDisplayArea() + .getWindowingMode()); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java index f542e290c701..0b58428e9bfb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java @@ -36,6 +36,7 @@ import android.platform.test.annotations.Presubmit; import android.view.PointerIcon; import android.view.SurfaceControl; import android.view.WindowManager; +import android.window.ScreenCapture; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -98,11 +99,11 @@ public class ScreenshotTests { .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB)) .apply(true); - SurfaceControl.LayerCaptureArgs args = new SurfaceControl.LayerCaptureArgs.Builder(secureSC) + ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC) .setCaptureSecureLayers(true) .setChildrenOnly(false) .build(); - SurfaceControl.ScreenshotHardwareBuffer hardwareBuffer = SurfaceControl.captureLayers(args); + ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = ScreenCapture.captureLayers(args); assertNotNull(hardwareBuffer); Bitmap screenshot = hardwareBuffer.asBitmap(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 646f43cbdb5c..54b33e9794b6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -210,10 +210,8 @@ public class SizeCompatTests extends WindowTestsBase { assertFitted(); // After the orientation of activity is changed, the display is rotated, the aspect - // ratio should be the same (bounds=[100, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). + // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); - // The notch is no longer on top. - assertEquals(appBounds, mActivity.getBounds()); // Activity max bounds are sandboxed. assertActivityMaxBoundsSandboxed(); @@ -467,8 +465,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation()); assertEquals(origBounds.width(), currentBounds.width()); - // The notch is on horizontal side, so current height changes from 1460 to 1400. - assertEquals(origBounds.height() - notchHeight, currentBounds.height()); // Make sure the app size is the same assertEquals(origAppBounds.width(), appBounds.width()); assertEquals(origAppBounds.height(), appBounds.height()); @@ -676,7 +672,8 @@ public class SizeCompatTests extends WindowTestsBase { // The non-resizable activity should not be size compat because the display support // changing windowing mode from fullscreen to freeform. - mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); assertFalse(activity.shouldCreateCompatDisplayInsets()); // Activity should not be sandboxed. @@ -2270,7 +2267,7 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); // Bounds are letterboxed to respect the provided max aspect ratio. - assertEquals(mActivity.getBounds(), new Rect(0, 850, 1000, 1950)); + assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 1100)); // Move activity to split screen which has landscape size. mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents */ false, "test"); @@ -2337,6 +2334,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() { + // Align to center so that we don't overlap with the status bar + mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) .setNotch(100) .build(); @@ -2353,7 +2352,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mRootWindowContainer.performSurfacePlacement(); Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); - assertEquals(mBounds, new Rect(0, 750, 1000, 1950)); + assertEquals(mBounds, new Rect(0, 900, 1000, 2000)); DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails( @@ -2453,7 +2452,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100), + /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(0, 0, 350, 700)); } @@ -2466,7 +2465,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -2481,7 +2480,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); @@ -2491,7 +2490,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -2504,7 +2503,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100), + /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700)); } @@ -2533,6 +2532,64 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testApplyAspectRatio_activityAlignWithParentAppVertical() { + // The display's app bounds will be (0, 100, 1000, 2350) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500) + .setCanRotate(false) + .setCutout(0, 100, 0, 150) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity height is 2100 and the display's app bounds height is 2250, so the activity + // can be aligned inside parentAppBounds + assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200)); + } + @Test + public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() { + // The display's app bounds will be (0, 100, 1000, 2150) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300) + .setCanRotate(false) + .setCutout(0, 100, 0, 150) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity height is 2100 and the display's app bounds height is 2050, so the activity + // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display + assertEquals(mActivity.getBounds(), display.getBounds()); + } + + @Test + public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() { + // The display's app bounds will be (100, 0, 2350, 1000) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000) + .setCanRotate(false) + .setCutout(100, 0, 150, 0) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity width is 2100 and the display's app bounds width is 2250, so the activity + // can be aligned inside parentAppBounds + assertEquals(mActivity.getBounds(), new Rect(175, 0, 2275, 1000)); + } + @Test + public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() { + // The display's app bounds will be (100, 0, 2150, 1000) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000) + .setCanRotate(false) + .setCutout(100, 0, 150, 0) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity width is 2100 and the display's app bounds width is 2050, so the activity + // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display + assertEquals(mActivity.getBounds(), display.getBounds()); + } + + @Test public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { // When activity width equals parent width, multiplier shouldn't have any effect. assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( @@ -2607,6 +2664,25 @@ public class SizeCompatTests extends WindowTestsBase { /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400)); } + @Test + public void testUpdateResolvedBoundsPosition_alignToTop() { + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) + .setNotch(notchHeight) + .build(); + setUpApp(display); + + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + + Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); + // The insets should be cut for aspect ratio and then added back because the appBounds + // are aligned to the top of the parentAppBounds + assertEquals(mBounds, new Rect(0, 0, 1000, 1200)); + assertEquals(appBounds, new Rect(0, notchHeight, 1000, 1200)); + } + private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox, Rect sizeCompatUnscaled, Rect sizeCompatScaled) { @@ -2954,7 +3030,7 @@ public class SizeCompatTests extends WindowTestsBase { rotateDisplay(mActivity.mDisplayContent, ROTATION_90); assertTrue(mActivity.inSizeCompatMode()); // Activity is in size compat mode but not scaled. - assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds()); + assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); } private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity( diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index ff0063c4ffa0..6c8a7ac0c613 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -361,7 +361,7 @@ public class SurfaceAnimatorTest extends WindowTestsBase { @Override public Builder makeAnimationLeash() { - return new SurfaceControl.Builder(mSession) { + return new Builder(mSession) { @Override public SurfaceControl build() { diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 9e6c4c58371b..f5fc5c13eb4c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -345,7 +345,7 @@ public class SystemServicesTestRule implements TestRule { // Set default display to be in fullscreen mode. Devices with PC feature may start their // default display in freeform mode but some of tests in WmTests have implicit assumption on // that the default display is in fullscreen mode. - display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN); + display.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(display); final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 22101c268a9c..aaf855f90122 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -1843,7 +1843,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { private TestDisplayContent createNewDisplayContent(int windowingMode) { final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP); - display.setWindowingMode(windowingMode); + display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode); display.setBounds(DISPLAY_BOUNDS); display.getConfiguration().densityDpi = DENSITY_DEFAULT; display.getConfiguration().orientation = ORIENTATION_LANDSCAPE; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 8e89d6f4e898..e352d762458a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1257,7 +1257,8 @@ public class TaskTests extends WindowTestsBase { final Task task = getTestTask(); task.setHasBeenVisible(false); - task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getDisplayContent().getDefaultTaskDisplayArea() + .setWindowingMode(WINDOWING_MODE_FREEFORM); task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); task.setHasBeenVisible(true); @@ -1273,7 +1274,9 @@ public class TaskTests extends WindowTestsBase { final Task task = getTestTask(); task.setHasBeenVisible(false); - task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + task.getDisplayContent() + .getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); final DisplayContent oldDisplay = task.getDisplayContent(); @@ -1313,7 +1316,8 @@ public class TaskTests extends WindowTestsBase { final Task task = getTestTask(); task.setHasBeenVisible(false); - task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getDisplayContent().getDefaultTaskDisplayArea() + .setWindowingMode(WINDOWING_MODE_FREEFORM); task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED); task.setHasBeenVisible(true); @@ -1330,7 +1334,8 @@ public class TaskTests extends WindowTestsBase { final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true) .setCreateParentTask(true).build().getRootTask(); task.setHasBeenVisible(false); - task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getDisplayContent().getDefaultTaskDisplayArea() + .setWindowingMode(WINDOWING_MODE_FREEFORM); task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); final Task leafTask = createTaskInRootTask(task, 0 /* userId */); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index aa3ca18073c3..bf1d1fa98249 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -143,11 +143,24 @@ class TestDisplayContent extends DisplayContent { mInfo.ownerUid = ownerUid; return this; } - Builder setNotch(int height) { + Builder setCutout(int left, int top, int right, int bottom) { + final int cutoutFillerSize = 80; + Rect boundLeft = left != 0 ? new Rect(0, 0, left, cutoutFillerSize) : null; + Rect boundTop = top != 0 ? new Rect(0, 0, cutoutFillerSize, top) : null; + Rect boundRight = right != 0 ? new Rect(mInfo.logicalWidth - right, 0, + mInfo.logicalWidth, cutoutFillerSize) : null; + Rect boundBottom = bottom != 0 + ? new Rect(0, mInfo.logicalHeight - bottom, cutoutFillerSize, + mInfo.logicalHeight) : null; + mInfo.displayCutout = new DisplayCutout( - Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null); + Insets.of(left, top, right, bottom), + boundLeft, boundTop, boundRight, boundBottom); return this; } + Builder setNotch(int height) { + return setCutout(0, height, 0, 0); + } Builder setStatusBarHeight(int height) { mStatusBarHeight = height; return this; diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 048cd7c68aa6..e2dff965ef96 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; @@ -31,7 +32,8 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -730,7 +732,7 @@ public class TransitionTests extends WindowTestsBase { assertTrue(ime.mToken.inTransition()); assertTrue(task.inTransition()); assertTrue(asyncRotationController.isTargetToken(decorToken)); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); screenDecor.setOrientationChanging(false); // Status bar finishes drawing before the start transaction. Its fade-in animation will be @@ -745,6 +747,7 @@ public class TransitionTests extends WindowTestsBase { // The transaction is committed, so fade-in animation for status bar is consumed. transactionCommittedListener.onTransactionCommitted(); assertFalse(asyncRotationController.isTargetToken(statusBar.mToken)); + assertShouldFreezeInsetsPosition(asyncRotationController, navBar, false); // Navigation bar finishes drawing after the start transaction, so its fade-in animation // can execute directly. @@ -780,7 +783,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); statusBar.setOrientationChanging(true); player.startTransition(); @@ -826,7 +829,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); assertTrue(app.getTask().inTransition()); player.start(); @@ -861,6 +864,15 @@ public class TransitionTests extends WindowTestsBase { assertNull(mDisplayContent.getAsyncRotationController()); } + private static void assertShouldFreezeInsetsPosition(AsyncRotationController controller, + WindowState w, boolean freeze) { + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { + // Non blast sync should never freeze insets position. + freeze = false; + } + assertEquals(freeze, controller.shouldFreezeInsetsPosition(w)); + } + @Test public void testDeferRotationForTransientLaunch() { final TestTransitionPlayer player = registerTestTransitionPlayer(); @@ -1065,12 +1077,14 @@ public class TransitionTests extends WindowTestsBase { } @Test - public void testIsEmbeddedChange() { + public void testFlagInTaskWithEmbeddedActivity() { final Transition transition = createTestTransition(TRANSIT_OPEN); final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; final ArraySet<WindowContainer> participants = transition.mParticipants; final Task task = createTask(mDisplayContent); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + assertFalse(nonEmbeddedActivity.isEmbedded()); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); mAtm.mTaskFragmentOrganizerController.registerOrganizer( ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); @@ -1085,20 +1099,72 @@ public class TransitionTests extends WindowTestsBase { changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); // End states. closingActivity.mVisibleRequested = false; openingActivity.mVisibleRequested = true; + nonEmbeddedActivity.mVisibleRequested = false; participants.add(closingActivity); participants.add(openingActivity); + participants.add(nonEmbeddedActivity); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task + // contains embedded activity. + assertEquals(3, info.getChanges().size()); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + assertTrue(info.getChanges().get(1).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + } + + @Test + public void testFlagFillsTask() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + // Set to multi-windowing mode in order to set bounds. + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final Rect taskBounds = new Rect(0, 0, 500, 1000); + task.setBounds(taskBounds); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + mAtm.mTaskFragmentOrganizerController.registerOrganizer( + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); + final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .setOrganizer(organizer) + .build(); + final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity(); + // Start states. + changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); + changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + // End states. + nonEmbeddedActivity.mVisibleRequested = false; + embeddedActivity.mVisibleRequested = true; + embeddedTf.setBounds(new Rect(0, 0, 500, 500)); + + participants.add(nonEmbeddedActivity); + participants.add(embeddedTf); final ArrayList<WindowContainer> targets = Transition.calculateTargets( participants, changes); final TransitionInfo info = Transition.calculateTransitionInfo( transition.mType, 0 /* flags */, targets, changes, mMockT); + // The embedded with bounds overridden should not have the flag. assertEquals(2, info.getChanges().size()); - assertTrue((info.getChanges().get(0).getFlags() & FLAG_IS_EMBEDDED) != 0); - assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0); + assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); + assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds()); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK)); } @Test 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 353b757e985e..9c9f5db68d09 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -97,6 +97,7 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.window.ITransitionPlayer; +import android.window.ScreenCapture; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskFragmentOrganizer; @@ -241,7 +242,7 @@ class WindowTestsBase extends SystemServiceTestsBase { // Ensure letterbox vertical position multiplier is not overridden on any device target. // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, // may be set on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.0f); // Ensure letterbox horizontal reachability treatment isn't overridden on any device target. // {@link com.android.internal.R.bool.config_letterboxIsHorizontalReachabilityEnabled}, // may be set on some device form factors. @@ -946,8 +947,8 @@ class WindowTestsBase extends SystemServiceTestsBase { /** Mocks the behavior of taking a snapshot. */ void mockSurfaceFreezerSnapshot(SurfaceFreezer surfaceFreezer) { - final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - mock(SurfaceControl.ScreenshotHardwareBuffer.class); + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + mock(ScreenCapture.ScreenshotHardwareBuffer.class); final HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); spyOn(surfaceFreezer); doReturn(screenshotBuffer).when(surfaceFreezer) diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 77fca451547d..a95fa5a4e978 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -147,7 +147,7 @@ public class ZOrderingTests extends WindowTestsBase { } private static class HierarchyRecordingBuilderFactory implements Function<SurfaceSession, - SurfaceControl.Builder> { + SurfaceControl.Builder> { private LayerRecordingTransaction mTransaction; HierarchyRecordingBuilderFactory(LayerRecordingTransaction transaction) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 4b9b4a9781de..4ee066c5c211 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1284,12 +1284,13 @@ public class VoiceInteractionManagerService extends SystemService { } } - @android.annotation.EnforcePermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) @Override public void startListeningFromMic( AudioFormat audioFormat, IMicrophoneHotwordDetectionVoiceInteractionCallback callback) throws RemoteException { + enforceCallingPermission(Manifest.permission.RECORD_AUDIO); + enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1349,12 +1350,13 @@ public class VoiceInteractionManagerService extends SystemService { } } - @android.annotation.EnforcePermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) @Override public void triggerHardwareRecognitionEventForTest( SoundTrigger.KeyphraseRecognitionEvent event, IHotwordRecognitionStatusCallback callback) throws RemoteException { + enforceCallingPermission(Manifest.permission.RECORD_AUDIO); + enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3023ec9661d4..70b51239b714 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -39,7 +39,6 @@ import android.service.carrier.CarrierService; import android.telecom.TelecomManager; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.data.ApnSetting; -import android.telephony.data.DataCallResponse; import android.telephony.gba.TlsParams; import android.telephony.gba.UaSecurityProtocolIdentifier; import android.telephony.ims.ImsReasonInfo; @@ -1136,27 +1135,6 @@ public class CarrierConfigManager { public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int"; /** - * The data call retry configuration for different types of APN. - * @hide - */ - public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS = - "carrier_data_call_retry_config_strings"; - - /** - * Delay in milliseconds between trying APN from the pool - * @hide - */ - public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG = - "carrier_data_call_apn_delay_default_long"; - - /** - * Faster delay in milliseconds between trying APN from the pool - * @hide - */ - public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG = - "carrier_data_call_apn_delay_faster_long"; - - /** * Delay in milliseconds for retrying APN after disconnect * @hide */ @@ -1164,21 +1142,6 @@ public class CarrierConfigManager { "carrier_data_call_apn_retry_after_disconnect_long"; /** - * The maximum times for telephony to retry data setup on the same APN requested by - * network through the data setup response retry timer - * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps - * asking device to retry data setup forever and causes power consumption issue. For infinite - * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}). - * - * Note if network does not suggest any retry timer, frameworks uses the retry configuration - * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could - * be configured there. - * @hide - */ - public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT = - "carrier_data_call_retry_network_requested_max_count_int"; - - /** * Data call setup permanent failure causes by the carrier */ public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = @@ -1239,19 +1202,6 @@ public class CarrierConfigManager { "carrier_metered_roaming_apn_types_strings"; /** - * APN types that are not allowed on cellular - * @hide - */ - public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY = - "carrier_wwan_disallowed_apn_types_string_array"; - - /** - * APN types that are not allowed on IWLAN - * @hide - */ - public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY = - "carrier_wlan_disallowed_apn_types_string_array"; - /** * CDMA carrier ERI (Enhanced Roaming Indicator) file name * @hide */ @@ -8470,7 +8420,6 @@ public class CarrierConfigManager { * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s, * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries. * - * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS * @hide */ public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY = @@ -8717,7 +8666,7 @@ public class CarrierConfigManager { * or EIMS--> * <item value="source=EUTRAN, target=IWLAN, type=disallowed, capabilities=IMS|EIMS"/> * <!-- Handover is always allowed in any condition. --> - * <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, + * <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN, * target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"/> * </string-array> * @@ -8872,27 +8821,13 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false); sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500); - sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{ - "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000," - + "320000:5000,640000:5000,1280000:5000,1800000:5000", - "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000," - + "320000:5000,640000:5000,1280000:5000,1800000:5000", - "ims:max_retries=10, 5000, 5000, 5000", - "others:max_retries=3, 5000, 5000, 5000"}); - sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000); - sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000); sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000); - sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3); sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml"); sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200); sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS, new String[]{"default", "mms", "dun", "supl"}); sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS, new String[]{"default", "mms", "dun", "supl"}); - sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY, - new String[]{""}); - sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY, - new String[]{""}); sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY, new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A, diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index cb985bf2cda5..0109ae614130 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -18,6 +18,7 @@ package android.telephony; import static android.text.TextUtils.formatSimple; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -37,6 +38,9 @@ import android.os.Build; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; +import android.telephony.SubscriptionManager.ProfileClass; +import android.telephony.SubscriptionManager.SimDisplayNameSource; +import android.telephony.SubscriptionManager.SubscriptionType; import android.telephony.SubscriptionManager.UsageSetting; import android.text.TextUtils; import android.util.DisplayMetrics; @@ -55,7 +59,6 @@ import java.util.Objects; * A Parcelable class for Subscription Information. */ public class SubscriptionInfo implements Parcelable { - /** * Size of text to render on the icon. */ @@ -65,162 +68,180 @@ public class SubscriptionInfo implements Parcelable { * Subscription Identifier, this is a device unique number * and not an index into an array */ - private int mId; + private final int mId; /** - * The GID for a SIM that maybe associated with this subscription, empty if unknown + * The ICCID of the SIM that is associated with this subscription, empty if unknown. */ - private String mIccId; + @NonNull + private final String mIccId; /** - * The index of the slot that currently contains the subscription - * and not necessarily unique and maybe INVALID_SLOT_ID if unknown + * The index of the SIM slot that currently contains the subscription and not necessarily unique + * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription + * is inactive. */ - private int mSimSlotIndex; + private final int mSimSlotIndex; /** - * The name displayed to the user that identifies this subscription + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. */ - private CharSequence mDisplayName; + @NonNull + private final CharSequence mDisplayName; /** - * String that identifies SPN/PLMN - * TODO : Add a new field that identifies only SPN for a sim + * The name displayed to the user that identifies subscription provider name. This name is the + * SPN displayed in status bar and many other places. Can't be renamed by the user. */ - private CharSequence mCarrierName; + @NonNull + private final CharSequence mCarrierName; /** * The subscription carrier id. + * * @see TelephonyManager#getSimCarrierId() */ - private int mCarrierId; + private final int mCarrierId; /** - * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN, - * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT. + * The source of the {@link #mCarrierName}. */ - private int mNameSource; + @SimDisplayNameSource + private final int mNameSource; /** - * The color to be used for tinting the icon when displaying to the user + * The color to be used for tinting the icon when displaying to the user. */ - private int mIconTint; + private final int mIconTint; /** - * A number presented to the user identify this subscription + * The number presented to the user identify this subscription. */ - private String mNumber; + @NonNull + private final String mNumber; /** - * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ - private int mDataRoaming; + private final int mDataRoaming; /** - * SIM icon bitmap cache + * SIM icon bitmap cache. */ - @Nullable private Bitmap mIconBitmap; + @Nullable + private Bitmap mIconBitmap; /** - * Mobile Country Code + * Mobile Country Code. */ - private String mMcc; + @Nullable + private final String mMcc; /** - * Mobile Network Code + * Mobile Network Code. */ - private String mMnc; + @Nullable + private final String mMnc; /** - * EHPLMNs associated with the subscription + * EHPLMNs associated with the subscription. */ - private String[] mEhplmns; + @NonNull + private final String[] mEhplmns; /** - * HPLMNs associated with the subscription + * HPLMNs associated with the subscription. */ - private String[] mHplmns; + @NonNull + private final String[] mHplmns; /** - * ISO Country code for the subscription's provider + * ISO Country code for the subscription's provider. */ - private String mCountryIso; + @NonNull + private final String mCountryIso; /** - * Whether the subscription is an embedded one. + * Whether the subscription is from eSIM. */ - private boolean mIsEmbedded; + private final boolean mIsEmbedded; /** - * The access rules for this subscription, if it is embedded and defines any. - * This does not include access rules for non-embedded subscriptions. + * The access rules for this subscription, if it is embedded and defines any. This does not + * include access rules for non-embedded subscriptions. */ @Nullable - private UiccAccessRule[] mNativeAccessRules; + private final UiccAccessRule[] mNativeAccessRules; /** * The carrier certificates for this subscription that are saved in carrier configs. * This does not include access rules from the Uicc, whether embedded or non-embedded. */ @Nullable - private UiccAccessRule[] mCarrierConfigAccessRules; + private final UiccAccessRule[] mCarrierConfigAccessRules; /** * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the * EID for an eUICC card. */ - private String mCardString; + @NonNull + private final String mCardString; /** - * The card ID of the SIM card. This maps uniquely to the card string. + * The card ID of the SIM card. This maps uniquely to {@link #mCardString}. */ - private int mCardId; + private final int mCardId; /** * Whether the subscription is opportunistic. */ - private boolean mIsOpportunistic; + private final boolean mIsOpportunistic; /** - * A UUID assigned to the subscription group. It returns null if not assigned. - * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details. + * A UUID assigned to the subscription group. {@code null} if not assigned. + * + * @see SubscriptionManager#createSubscriptionGroup(List) */ @Nullable - private ParcelUuid mGroupUUID; + private final ParcelUuid mGroupUuid; /** - * A package name that specifies who created the group. Null if mGroupUUID is null. + * A package name that specifies who created the group. Empty if not available. */ - private String mGroupOwner; + @NonNull + private final String mGroupOwner; /** - * Whether group of the subscription is disabled. - * This is only useful if it's a grouped opportunistic subscription. In this case, if all - * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM - * or deactivated eSIM profile), we should disable this opportunistic subscription. + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions + * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable + * this opportunistic subscription. */ - private boolean mIsGroupDisabled = false; + private final boolean mIsGroupDisabled; /** - * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL - * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET. - * A profile on the eUICC can be defined as test, operational, provisioning, or unset. - * The profile class will be populated from the profile metadata if present. Otherwise, - * the profile class defaults to unset if there is no profile metadata or the subscription - * is not on an eUICC ({@link #isEmbedded} returns false). + * The profile class populated from the profile metadata if present. Otherwise, + * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no + * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns + * {@code false}). */ - private int mProfileClass; + @ProfileClass + private final int mProfileClass; /** - * Type of subscription + * Type of the subscription. */ - private int mSubscriptionType; + @SubscriptionType + private final int mType; /** * Whether uicc applications are configured to enable or disable. * By default it's true. */ - private boolean mAreUiccApplicationsEnabled = true; + private final boolean mAreUiccApplicationsEnabled; /** * The port index of the Uicc card. @@ -230,25 +251,16 @@ public class SubscriptionInfo implements Parcelable { /** * Subscription's preferred usage setting. */ - private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN; - - /** - * Public copy constructor. - * @hide - */ - public SubscriptionInfo(SubscriptionInfo info) { - this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName, - info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap, - info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules, - info.mCardString, info.mCardId, info.mIsOpportunistic, - info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled, - info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner, - info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled); - } + @UsageSetting + private final int mUsageSetting; /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -262,7 +274,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -276,7 +292,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -293,7 +313,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -311,49 +335,94 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId, - boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled, + boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled, int carrierId, int profileClass, int subType, @Nullable String groupOwner, @Nullable UiccAccessRule[] carrierConfigAccessRules, boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; - this.mDisplayName = displayName; + this.mDisplayName = displayName; this.mCarrierName = carrierName; this.mNameSource = nameSource; this.mIconTint = iconTint; this.mNumber = number; this.mDataRoaming = roaming; this.mIconBitmap = icon; - this.mMcc = mcc; - this.mMnc = mnc; - this.mCountryIso = countryIso; + this.mMcc = TextUtils.emptyIfNull(mcc); + this.mMnc = TextUtils.emptyIfNull(mnc); + this.mHplmns = null; + this.mEhplmns = null; + this.mCountryIso = TextUtils.emptyIfNull(countryIso); this.mIsEmbedded = isEmbedded; this.mNativeAccessRules = nativeAccessRules; - this.mCardString = cardString; + this.mCardString = TextUtils.emptyIfNull(cardString); this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; - this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID); + this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid); this.mIsGroupDisabled = isGroupDisabled; this.mCarrierId = carrierId; this.mProfileClass = profileClass; - this.mSubscriptionType = subType; - this.mGroupOwner = groupOwner; + this.mType = subType; + this.mGroupOwner = TextUtils.emptyIfNull(groupOwner); this.mCarrierConfigAccessRules = carrierConfigAccessRules; this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled; this.mPortIndex = portIndex; this.mUsageSetting = usageSetting; } + /** - * @return the subscription ID. + * Constructor from builder. + * + * @param builder Builder of {@link SubscriptionInfo}. + */ + private SubscriptionInfo(@NonNull Builder builder) { + this.mId = builder.mId; + this.mIccId = builder.mIccId; + this.mSimSlotIndex = builder.mSimSlotIndex; + this.mDisplayName = builder.mDisplayName; + this.mCarrierName = builder.mCarrierName; + this.mNameSource = builder.mNameSource; + this.mIconTint = builder.mIconTint; + this.mNumber = builder.mNumber; + this.mDataRoaming = builder.mDataRoaming; + this.mIconBitmap = builder.mIconBitmap; + this.mMcc = builder.mMcc; + this.mMnc = builder.mMnc; + this.mEhplmns = builder.mEhplmns; + this.mHplmns = builder.mHplmns; + this.mCountryIso = builder.mCountryIso; + this.mIsEmbedded = builder.mIsEmbedded; + this.mNativeAccessRules = builder.mNativeAccessRules; + this.mCardString = builder.mCardString; + this.mCardId = builder.mCardId; + this.mIsOpportunistic = builder.mIsOpportunistic; + this.mGroupUuid = builder.mGroupUuid; + this.mIsGroupDisabled = builder.mIsGroupDisabled; + this.mCarrierId = builder.mCarrierId; + this.mProfileClass = builder.mProfileClass; + this.mType = builder.mType; + this.mGroupOwner = builder.mGroupOwner; + this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules; + this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled; + this.mPortIndex = builder.mPortIndex; + this.mUsageSetting = builder.mUsageSetting; + } + + /** + * @return The subscription ID. */ public int getSubscriptionId() { - return this.mId; + return mId; } /** @@ -370,78 +439,56 @@ public class SubscriptionInfo implements Parcelable { * @return the ICC ID, or an empty string if one of these requirements is not met */ public String getIccId() { - return this.mIccId; + return mIccId; } /** - * @hide - */ - public void clearIccId() { - this.mIccId = ""; - } - - /** - * @return the slot index of this Subscription's SIM card. + * @return The index of the SIM slot that currently contains the subscription and not + * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or + * the subscription is inactive. */ public int getSimSlotIndex() { - return this.mSimSlotIndex; + return mSimSlotIndex; } /** - * @return the carrier id of this Subscription carrier. + * @return The carrier id of this subscription carrier. + * * @see TelephonyManager#getSimCarrierId() */ public int getCarrierId() { - return this.mCarrierId; + return mCarrierId; } /** - * @return the name displayed to the user that identifies this subscription + * @return The name displayed to the user that identifies this subscription. This name is + * used in Settings page and can be renamed by the user. + * + * @see #getCarrierName() */ public CharSequence getDisplayName() { - return this.mDisplayName; + return mDisplayName; } /** - * Sets the name displayed to the user that identifies this subscription - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setDisplayName(CharSequence name) { - this.mDisplayName = name; - } - - /** - * @return the name displayed to the user that identifies Subscription provider name + * @return The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + * + * @see #getDisplayName() */ public CharSequence getCarrierName() { - return this.mCarrierName; - } - - /** - * Sets the name displayed to the user that identifies Subscription provider name - * @hide - */ - public void setCarrierName(CharSequence name) { - this.mCarrierName = name; + return mCarrierName; } /** - * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or - * NAME_SOURCE_USER_INPUT. + * @return The source of the {@link #getCarrierName()}. + * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SimDisplayNameSource public int getNameSource() { - return this.mNameSource; - } - - /** - * @hide - */ - public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) { - mEhplmns = ehplmns; - mHplmns = hplmns; + return mNameSource; } /** @@ -499,15 +546,6 @@ public class SubscriptionInfo implements Parcelable { } /** - * Sets the color displayed to the user that identifies this subscription - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setIconTint(int iconTint) { - this.mIconTint = iconTint; - } - - /** * Returns the number of this subscription. * * Starting with API level 30, returns the number of this subscription if the calling app meets @@ -533,28 +571,23 @@ public class SubscriptionInfo implements Parcelable { } /** - * @hide - */ - public void clearNumber() { - mNumber = ""; - } - - /** - * @return the data roaming state for this subscription, either - * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ public int getDataRoaming() { - return this.mDataRoaming; + return mDataRoaming; } /** - * @return the MCC. + * @return The mobile country code. + * * @deprecated Use {@link #getMccString()} instead. */ @Deprecated public int getMcc() { try { - return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc); + return mMcc == null ? 0 : Integer.parseInt(mMcc); } catch (NumberFormatException e) { Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number"); return 0; @@ -562,13 +595,14 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the MNC. + * @return The mobile network code. + * * @deprecated Use {@link #getMncString()} instead. */ @Deprecated public int getMnc() { try { - return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc); + return mMnc == null ? 0 : Integer.parseInt(mMnc); } catch (NumberFormatException e) { Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number"); return 0; @@ -576,36 +610,40 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return The MCC, as a string. + * @return The mobile country code. */ - public @Nullable String getMccString() { - return this.mMcc; + @Nullable + public String getMccString() { + return mMcc; } /** - * @return The MNC, as a string. + * @return The mobile network code. */ - public @Nullable String getMncString() { - return this.mMnc; + @Nullable + public String getMncString() { + return mMnc; } /** - * @return the ISO country code + * @return The ISO country code. Empty if not available. */ public String getCountryIso() { - return this.mCountryIso; + return mCountryIso; } - /** @return whether the subscription is an eUICC one. */ + /** + * @return {@code true} if the subscription is from eSIM. + */ public boolean isEmbedded() { - return this.mIsEmbedded; + return mIsEmbedded; } /** * An opportunistic subscription connects to a network that is * limited in functionality and / or coverage. * - * @return whether subscription is opportunistic. + * @return Whether subscription is opportunistic. */ public boolean isOpportunistic() { return mIsOpportunistic; @@ -617,23 +655,18 @@ public class SubscriptionInfo implements Parcelable { * Such that those subscriptions will have some affiliated behaviors such as opportunistic * subscription may be invisible to the user. * - * @return group UUID a String of group UUID if it belongs to a group. Otherwise - * it will return null. + * @return Group UUID a String of group UUID if it belongs to a group. Otherwise + * {@code null}. */ - public @Nullable ParcelUuid getGroupUuid() { - return mGroupUUID; - } - - /** - * @hide - */ - public void clearGroupUuid() { - this.mGroupUUID = null; + @Nullable + public ParcelUuid getGroupUuid() { + return mGroupUuid; } /** * @hide */ + @NonNull public List<String> getEhplmns() { return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns); } @@ -641,36 +674,45 @@ public class SubscriptionInfo implements Parcelable { /** * @hide */ + @NonNull public List<String> getHplmns() { return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns); } /** - * Return owner package of group the subscription belongs to. + * @return The owner package of group the subscription belongs to. * * @hide */ - public @Nullable String getGroupOwner() { + @NonNull + public String getGroupOwner() { return mGroupOwner; } /** - * @return the profile class of this subscription. + * @return The profile class populated from the profile metadata if present. Otherwise, + * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no + * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return + * {@code false}). + * * @hide */ @SystemApi - public @SubscriptionManager.ProfileClass int getProfileClass() { - return this.mProfileClass; + @ProfileClass + public int getProfileClass() { + return mProfileClass; } /** * This method returns the type of a subscription. It can be * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. - * @return the type of subscription + * + * @return The type of the subscription. */ - public @SubscriptionManager.SubscriptionType int getSubscriptionType() { - return mSubscriptionType; + @SubscriptionType + public int getSubscriptionType() { + return mType; } /** @@ -679,7 +721,7 @@ public class SubscriptionInfo implements Parcelable { * returns true). * * @param context Context of the application to check. - * @return whether the app is authorized to manage this subscription per its metadata. + * @return Whether the app is authorized to manage this subscription per its metadata. * @hide * @deprecated - Do not use. */ @@ -700,7 +742,7 @@ public class SubscriptionInfo implements Parcelable { */ @Deprecated public boolean canManageSubscription(Context context, String packageName) { - List<UiccAccessRule> allAccessRules = getAllAccessRules(); + List<UiccAccessRule> allAccessRules = getAccessRules(); if (allAccessRules == null) { return false; } @@ -723,24 +765,14 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who - * is authorized to manage this subscription. - * TODO and fix it properly in R / master: either deprecate this and have 3 APIs - * native + carrier + all, or have this return all by default. + * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to + * manage this subscription. + * * @hide */ @SystemApi - public @Nullable List<UiccAccessRule> getAccessRules() { - if (mNativeAccessRules == null) return null; - return Arrays.asList(mNativeAccessRules); - } - - /** - * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs - * dictating who is authorized to manage this subscription. - * @hide - */ - public @Nullable List<UiccAccessRule> getAllAccessRules() { + @Nullable + public List<UiccAccessRule> getAccessRules() { List<UiccAccessRule> merged = new ArrayList<>(); if (mNativeAccessRules != null) { merged.addAll(getAccessRules()); @@ -762,50 +794,38 @@ public class SubscriptionInfo implements Parcelable { * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile * owner access is deprecated and will be removed in a future release. * - * @return the card string of the SIM card which contains the subscription or an empty string + * @return The card string of the SIM card which contains the subscription or an empty string * if these requirements are not met. The card string is the ICCID for UICCs or the EID for * eUICCs. + * * @hide - * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java */ + @NonNull public String getCardString() { - return this.mCardString; + return mCardString; } /** - * @hide - */ - public void clearCardString() { - this.mCardString = ""; - } - - /** - * Returns the card ID of the SIM card which contains the subscription (see - * {@link UiccCardInfo#getCardId()}. - * @return the cardId + * @return The card ID of the SIM card which contains the subscription. + * + * @see UiccCardInfo#getCardId(). */ public int getCardId() { - return this.mCardId; + return mCardId; } /** - * Returns the port index of the SIM card which contains the subscription. - * - * @return the portIndex + * @return The port index of the SIM card which contains the subscription. */ public int getPortIndex() { - return this.mPortIndex; - } - - /** - * Set whether the subscription's group is disabled. - * @hide - */ - public void setGroupDisabled(boolean isGroupDisabled) { - this.mIsGroupDisabled = isGroupDisabled; + return mPortIndex; } /** - * Return whether the subscription's group is disabled. + * @return {@code true} if the group of the subscription is disabled. This is only useful if + * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we + * should disable this opportunistic subscription. + * * @hide */ @SystemApi @@ -814,7 +834,7 @@ public class SubscriptionInfo implements Parcelable { } /** - * Return whether uicc applications are set to be enabled or disabled. + * @return {@code true} if Uicc applications are set to be enabled or disabled. * @hide */ @SystemApi @@ -825,56 +845,50 @@ public class SubscriptionInfo implements Parcelable { /** * Get the usage setting for this subscription. * - * @return the usage setting used for this subscription. + * @return The usage setting used for this subscription. */ - public @UsageSetting int getUsageSetting() { + @UsageSetting + public int getUsageSetting() { return mUsageSetting; } - public static final @android.annotation.NonNull - Parcelable.Creator<SubscriptionInfo> CREATOR = - new Parcelable.Creator<SubscriptionInfo>() { + @NonNull + public static final Parcelable.Creator<SubscriptionInfo> CREATOR = + new Parcelable.Creator<SubscriptionInfo>() { @Override public SubscriptionInfo createFromParcel(Parcel source) { - int id = source.readInt(); - String iccId = source.readString(); - int simSlotIndex = source.readInt(); - CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - int nameSource = source.readInt(); - int iconTint = source.readInt(); - String number = source.readString(); - int dataRoaming = source.readInt(); - String mcc = source.readString(); - String mnc = source.readString(); - String countryIso = source.readString(); - boolean isEmbedded = source.readBoolean(); - UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR); - String cardString = source.readString(); - int cardId = source.readInt(); - int portId = source.readInt(); - boolean isOpportunistic = source.readBoolean(); - String groupUUID = source.readString(); - boolean isGroupDisabled = source.readBoolean(); - int carrierid = source.readInt(); - int profileClass = source.readInt(); - int subType = source.readInt(); - String[] ehplmns = source.createStringArray(); - String[] hplmns = source.createStringArray(); - String groupOwner = source.readString(); - UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray( - UiccAccessRule.CREATOR); - boolean areUiccApplicationsEnabled = source.readBoolean(); - int usageSetting = source.readInt(); - - SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName, - carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null, - mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId, - isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType, - groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, - portId, usageSetting); - info.setAssociatedPlmns(ehplmns, hplmns); - return info; + return new Builder() + .setId(source.readInt()) + .setIccId(source.readString()) + .setSimSlotIndex(source.readInt()) + .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source)) + .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source)) + .setNameSource(source.readInt()) + .setIconTint(source.readInt()) + .setNumber(source.readString()) + .setDataRoaming(source.readInt()) + .setMcc(source.readString()) + .setMnc(source.readString()) + .setCountryIso(source.readString()) + .setEmbedded(source.readBoolean()) + .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR)) + .setCardString(source.readString()) + .setCardId(source.readInt()) + .setPortIndex(source.readInt()) + .setOpportunistic(source.readBoolean()) + .setGroupUuid(source.readString8()) + .setGroupDisabled(source.readBoolean()) + .setCarrierId(source.readInt()) + .setProfileClass(source.readInt()) + .setType(source.readInt()) + .setEhplmns(source.createStringArray()) + .setHplmns(source.createStringArray()) + .setGroupOwner(source.readString()) + .setCarrierConfigAccessRules(source.createTypedArray( + UiccAccessRule.CREATOR)) + .setUiccApplicationsEnabled(source.readBoolean()) + .setUsageSetting(source.readInt()) + .build(); } @Override @@ -904,11 +918,11 @@ public class SubscriptionInfo implements Parcelable { dest.writeInt(mCardId); dest.writeInt(mPortIndex); dest.writeBoolean(mIsOpportunistic); - dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString()); + dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString()); dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); - dest.writeInt(mSubscriptionType); + dest.writeInt(mType); dest.writeStringArray(mEhplmns); dest.writeStringArray(mHplmns); dest.writeString(mGroupOwner); @@ -923,6 +937,11 @@ public class SubscriptionInfo implements Parcelable { } /** + * Get ICCID stripped PII information on user build. + * + * @param iccId The original ICCID. + * @return The stripped string. + * * @hide */ public static String givePrintableIccid(String iccId) { @@ -951,12 +970,12 @@ public class SubscriptionInfo implements Parcelable { + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " portIndex=" + mPortIndex - + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID + + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid + " isGroupDisabled=" + mIsGroupDisabled + " profileClass=" + mProfileClass + " ehplmns=" + Arrays.toString(mEhplmns) + " hplmns=" + Arrays.toString(mHplmns) - + " subscriptionType=" + mSubscriptionType + + " mType=" + mType + " groupOwner=" + mGroupOwner + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules) + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled @@ -966,7 +985,7 @@ public class SubscriptionInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, - mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, + mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules), mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting); @@ -974,16 +993,9 @@ public class SubscriptionInfo implements Parcelable { @Override public boolean equals(Object obj) { - if (obj == null) return false; - if (obj == this) return true; - - SubscriptionInfo toCompare; - try { - toCompare = (SubscriptionInfo) obj; - } catch (ClassCastException ex) { - return false; - } - + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + SubscriptionInfo toCompare = (SubscriptionInfo) obj; return mId == toCompare.mId && mSimSlotIndex == toCompare.mSimSlotIndex && mNameSource == toCompare.mNameSource @@ -994,7 +1006,7 @@ public class SubscriptionInfo implements Parcelable { && mIsGroupDisabled == toCompare.mIsGroupDisabled && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled && mCarrierId == toCompare.mCarrierId - && Objects.equals(mGroupUUID, toCompare.mGroupUUID) + && Objects.equals(mGroupUuid, toCompare.mGroupUuid) && Objects.equals(mIccId, toCompare.mIccId) && Objects.equals(mNumber, toCompare.mNumber) && Objects.equals(mMcc, toCompare.mMcc) @@ -1012,4 +1024,629 @@ public class SubscriptionInfo implements Parcelable { && Arrays.equals(mHplmns, toCompare.mHplmns) && mUsageSetting == toCompare.mUsageSetting; } + + /** + * The builder class of {@link SubscriptionInfo}. + * + * @hide + */ + public static class Builder { + /** + * The subscription id. + */ + private int mId = 0; + + /** + * The ICCID of the SIM that is associated with this subscription, empty if unknown. + */ + @NonNull + private String mIccId = ""; + + /** + * The index of the SIM slot that currently contains the subscription and not necessarily + * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the + * subscription is inactive. + */ + private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + + /** + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. + */ + @NonNull + private CharSequence mDisplayName = ""; + + /** + * The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + */ + @NonNull + private CharSequence mCarrierName = ""; + + /** + * The source of the carrier name. + */ + @SimDisplayNameSource + private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID; + + /** + * The color to be used for tinting the icon when displaying to the user. + */ + private int mIconTint = 0; + + /** + * The number presented to the user identify this subscription. + */ + @NonNull + private String mNumber = ""; + + /** + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + */ + private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE; + + /** + * SIM icon bitmap cache. + */ + @Nullable + private Bitmap mIconBitmap = null; + + /** + * The mobile country code. + */ + @Nullable + private String mMcc = null; + + /** + * The mobile network code. + */ + @Nullable + private String mMnc = null; + + /** + * EHPLMNs associated with the subscription. + */ + @NonNull + private String[] mEhplmns = new String[0]; + + /** + * HPLMNs associated with the subscription. + */ + @NonNull + private String[] mHplmns = new String[0]; + + /** + * The ISO Country code for the subscription's provider. + */ + @NonNull + private String mCountryIso = ""; + + /** + * Whether the subscription is from eSIM. + */ + private boolean mIsEmbedded = false; + + /** + * The native access rules for this subscription, if it is embedded and defines any. This + * does not include access rules for non-embedded subscriptions. + */ + @Nullable + private UiccAccessRule[] mNativeAccessRules = null; + + /** + * The card string of the SIM card. + */ + @NonNull + private String mCardString = ""; + + /** + * The card ID of the SIM card which contains the subscription. + */ + private int mCardId = -1; + + /** + * Whether the subscription is opportunistic or not. + */ + private boolean mIsOpportunistic = false; + + /** + * The group UUID of the subscription group. + */ + @Nullable + private ParcelUuid mGroupUuid = null; + + /** + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), + * we should disable this opportunistic subscription. + */ + private boolean mIsGroupDisabled = false; + + /** + * The carrier id. + * + * @see TelephonyManager#getSimCarrierId() + */ + private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; + + /** + * The profile class populated from the profile metadata if present. Otherwise, the profile + * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile + * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns + * {@code false}). + */ + @ProfileClass + private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET; + + /** + * The subscription type. + */ + @SubscriptionType + private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM; + + /** + * The owner package of group the subscription belongs to. + */ + @NonNull + private String mGroupOwner = ""; + + /** + * The carrier certificates for this subscription that are saved in carrier configs. + * This does not include access rules from the Uicc, whether embedded or non-embedded. + */ + @Nullable + private UiccAccessRule[] mCarrierConfigAccessRules = null; + + /** + * Whether Uicc applications are configured to enable or not. + */ + private boolean mAreUiccApplicationsEnabled = true; + + /** + * the port index of the Uicc card. + */ + private int mPortIndex = 0; + + /** + * Subscription's preferred usage setting. + */ + @UsageSetting + private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN; + + /** + * Default constructor. + */ + public Builder() { + } + + /** + * Constructor from {@link SubscriptionInfo}. + * + * @param info The subscription info. + */ + public Builder(@NonNull SubscriptionInfo info) { + mId = info.mId; + mIccId = info.mIccId; + mSimSlotIndex = info.mSimSlotIndex; + mDisplayName = info.mDisplayName; + mCarrierName = info.mCarrierName; + mNameSource = info.mNameSource; + mIconTint = info.mIconTint; + mNumber = info.mNumber; + mDataRoaming = info.mDataRoaming; + mIconBitmap = info.mIconBitmap; + mMcc = info.mMcc; + mMnc = info.mMnc; + mEhplmns = info.mEhplmns; + mHplmns = info.mHplmns; + mCountryIso = info.mCountryIso; + mIsEmbedded = info.mIsEmbedded; + mNativeAccessRules = info.mNativeAccessRules; + mCardString = info.mCardString; + mCardId = info.mCardId; + mIsOpportunistic = info.mIsOpportunistic; + mGroupUuid = info.mGroupUuid; + mIsGroupDisabled = info.mIsGroupDisabled; + mCarrierId = info.mCarrierId; + mProfileClass = info.mProfileClass; + mType = info.mType; + mGroupOwner = info.mGroupOwner; + mCarrierConfigAccessRules = info.mCarrierConfigAccessRules; + mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled; + mPortIndex = info.mPortIndex; + mUsageSetting = info.mUsageSetting; + } + + /** + * Set the subscription id. + * + * @param id The subscription id. + * @return The builder. + */ + @NonNull + public Builder setId(int id) { + mId = id; + return this; + } + + /** + * Set the ICCID of the SIM that is associated with this subscription. + * + * @param iccId The ICCID of the SIM that is associated with this subscription. + * @return The builder. + */ + @NonNull + public Builder setIccId(@Nullable String iccId) { + mIccId = TextUtils.emptyIfNull(iccId); + return this; + } + + /** + * Set the SIM index of the slot that currently contains the subscription. Set to + * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive. + * + * @param simSlotIndex The SIM slot index. + * @return The builder. + */ + @NonNull + public Builder setSimSlotIndex(int simSlotIndex) { + mSimSlotIndex = simSlotIndex; + return this; + } + + /** + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. + * + * @param displayName The display name. + * @return The builder. + */ + @NonNull + public Builder setDisplayName(@Nullable CharSequence displayName) { + mDisplayName = displayName == null ? "" : displayName; + return this; + } + + /** + * The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + * + * @param carrierName The carrier name. + * @return The builder. + */ + @NonNull + public Builder setCarrierName(@Nullable CharSequence carrierName) { + mCarrierName = carrierName == null ? "" : carrierName; + return this; + } + + /** + * Set the source of the carrier name. + * + * @param nameSource The source of the carrier name. + * @return The builder. + */ + @NonNull + public Builder setNameSource(@SimDisplayNameSource int nameSource) { + mNameSource = nameSource; + return this; + } + + /** + * Set the color to be used for tinting the icon when displaying to the user. + * + * @param iconTint The color to be used for tinting the icon when displaying to the user. + * @return The builder. + */ + @NonNull + public Builder setIconTint(int iconTint) { + mIconTint = iconTint; + return this; + } + + /** + * Set the number presented to the user identify this subscription. + * + * @param number the number presented to the user identify this subscription. + * @return The builder. + */ + @NonNull + public Builder setNumber(@Nullable String number) { + mNumber = TextUtils.emptyIfNull(number); + return this; + } + + /** + * Set whether user enables data roaming for this subscription or not. + * + * @param dataRoaming Data roaming mode. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE} + * @return The builder. + */ + @NonNull + public Builder setDataRoaming(int dataRoaming) { + mDataRoaming = dataRoaming; + return this; + } + + /** + * Set SIM icon bitmap cache. + * + * @param iconBitmap SIM icon bitmap cache. + * @return The builder. + */ + @NonNull + public Builder setIcon(@Nullable Bitmap iconBitmap) { + mIconBitmap = iconBitmap; + return this; + } + + /** + * Set the mobile country code. + * + * @param mcc The mobile country code. + * @return The builder. + */ + @NonNull + public Builder setMcc(@Nullable String mcc) { + mMcc = mcc; + return this; + } + + /** + * Set the mobile network code. + * + * @param mnc Mobile network code. + * @return The builder. + */ + @NonNull + public Builder setMnc(@Nullable String mnc) { + mMnc = mnc; + return this; + } + + /** + * Set EHPLMNs associated with the subscription. + * + * @param ehplmns EHPLMNs associated with the subscription. + * @return The builder. + */ + @NonNull + public Builder setEhplmns(@Nullable String[] ehplmns) { + mEhplmns = ehplmns == null ? new String[0] : ehplmns; + return this; + } + + /** + * Set HPLMNs associated with the subscription. + * + * @param hplmns HPLMNs associated with the subscription. + * @return The builder. + */ + @NonNull + public Builder setHplmns(@Nullable String[] hplmns) { + mHplmns = hplmns == null ? new String[0] : hplmns; + return this; + } + + /** + * Set the ISO Country code for the subscription's provider. + * + * @param countryIso The ISO Country code for the subscription's provider. + * @return The builder. + */ + @NonNull + public Builder setCountryIso(@Nullable String countryIso) { + mCountryIso = TextUtils.emptyIfNull(countryIso); + return this; + } + + /** + * Set whether the subscription is from eSIM or not. + * + * @param isEmbedded {@code true} if the subscription is from eSIM. + * @return The builder. + */ + @NonNull + public Builder setEmbedded(boolean isEmbedded) { + mIsEmbedded = isEmbedded; + return this; + } + + /** + * Set the native access rules for this subscription, if it is embedded and defines any. + * This does not include access rules for non-embedded subscriptions. + * + * @param nativeAccessRules The native access rules for this subscription. + * @return The builder. + */ + @NonNull + public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) { + mNativeAccessRules = nativeAccessRules; + return this; + } + + /** + * Set the card string of the SIM card. + * + * @param cardString The card string of the SIM card. + * @return The builder. + * + * @see #getCardString() + */ + @NonNull + public Builder setCardString(@Nullable String cardString) { + mCardString = TextUtils.emptyIfNull(cardString); + return this; + } + + /** + * Set the card ID of the SIM card which contains the subscription. + * + * @param cardId The card ID of the SIM card which contains the subscription. + * @return The builder. + */ + @NonNull + public Builder setCardId(int cardId) { + mCardId = cardId; + return this; + } + + /** + * Set whether the subscription is opportunistic or not. + * + * @param isOpportunistic {@code true} if the subscription is opportunistic. + * @return The builder. + */ + @NonNull + public Builder setOpportunistic(boolean isOpportunistic) { + mIsOpportunistic = isOpportunistic; + return this; + } + + /** + * Set the group UUID of the subscription group. + * + * @param groupUuid The group UUID. + * @return The builder. + * + * @see #getGroupUuid() + */ + @NonNull + public Builder setGroupUuid(@Nullable String groupUuid) { + mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid); + return this; + } + + /** + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), + * we should disable this opportunistic subscription. + * + * @param isGroupDisabled {@code true} if group of the subscription is disabled. + * @return The builder. + */ + @NonNull + public Builder setGroupDisabled(boolean isGroupDisabled) { + mIsGroupDisabled = isGroupDisabled; + return this; + } + + /** + * Set the subscription carrier id. + * + * @param carrierId The carrier id. + * @return The builder + * + * @see TelephonyManager#getSimCarrierId() + */ + @NonNull + public Builder setCarrierId(int carrierId) { + mCarrierId = carrierId; + return this; + } + + /** + * Set the profile class populated from the profile metadata if present. + * + * @param profileClass the profile class populated from the profile metadata if present. + * @return The builder + * + * @see #getProfileClass() + */ + @NonNull + public Builder setProfileClass(@ProfileClass int profileClass) { + mProfileClass = profileClass; + return this; + } + + /** + * Set the subscription type. + * + * @param type Subscription type. + * @return The builder. + */ + @NonNull + public Builder setType(@SubscriptionType int type) { + mType = type; + return this; + } + + /** + * Set the owner package of group the subscription belongs to. + * + * @param groupOwner Owner package of group the subscription belongs to. + * @return The builder. + */ + @NonNull + public Builder setGroupOwner(@Nullable String groupOwner) { + mGroupOwner = TextUtils.emptyIfNull(groupOwner); + return this; + } + + /** + * Set the carrier certificates for this subscription that are saved in carrier configs. + * This does not include access rules from the Uicc, whether embedded or non-embedded. + * + * @param carrierConfigAccessRules The carrier certificates for this subscription + * @return The builder. + */ + @NonNull + public Builder setCarrierConfigAccessRules( + @Nullable UiccAccessRule[] carrierConfigAccessRules) { + mCarrierConfigAccessRules = carrierConfigAccessRules; + return this; + } + + /** + * Set whether Uicc applications are configured to enable or not. + * + * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to + * enable. + * @return The builder. + */ + @NonNull + public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) { + mAreUiccApplicationsEnabled = uiccApplicationsEnabled; + return this; + } + + /** + * Set the port index of the Uicc card. + * + * @param portIndex The port index of the Uicc card. + * @return The builder. + */ + @NonNull + public Builder setPortIndex(int portIndex) { + mPortIndex = portIndex; + return this; + } + + /** + * Set subscription's preferred usage setting. + * + * @param usageSetting Subscription's preferred usage setting. + * @return The builder. + */ + @NonNull + public Builder setUsageSetting(@UsageSetting int usageSetting) { + mUsageSetting = usageSetting; + return this; + } + + /** + * Build the {@link SubscriptionInfo}. + * + * @return The {@link SubscriptionInfo} instance. + */ + public SubscriptionInfo build() { + return new SubscriptionInfo(this); + } + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4fb65874044f..f4be96417cb9 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3099,7 +3099,7 @@ public class SubscriptionManager { @SystemApi public boolean canManageSubscription(@NonNull SubscriptionInfo info, @NonNull String packageName) { - if (info == null || info.getAllAccessRules() == null || packageName == null) { + if (info == null || info.getAccessRules() == null || packageName == null) { return false; } PackageManager packageManager = mContext.getPackageManager(); @@ -3111,7 +3111,7 @@ public class SubscriptionManager { logd("Unknown package: " + packageName); return false; } - for (UiccAccessRule rule : info.getAllAccessRules()) { + for (UiccAccessRule rule : info.getAccessRules()) { if (rule.getCarrierPrivilegeStatus(packageInfo) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return true; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 3032babee07f..850d26801f17 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2548,9 +2548,6 @@ interface ITelephony { CellIdentity getLastKnownCellIdentity(int subId, String callingPackage, String callingFeatureId); - /** Check if telephony new data stack is enabled. */ - boolean isUsingNewDataStack(); - /** * @return true if the modem service is set successfully, false otherwise. */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt new file mode 100644 index 000000000000..ea5a5f8b3927 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class GameAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME, + component: ComponentNameMatcher = + ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy, +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + /** + * Swipes down in the mock game app. + * + * @return true if the swipe operation is successful. + */ + fun swipeDown(): Boolean { + val gameView = uiDevice.wait( + Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS) + require(gameView != null) { "Mock game app view not found." } + + val bound = gameView.getVisibleBounds() + return uiDevice.swipe( + bound.centerX(), bound.top, bound.centerX(), bound.centerY(), SWIPE_STEPS) + } + + /** + * Switches to a recent app by quick switch gesture. This function can be used in both portrait + * and landscape mode. + * + * @param wmHelper Helper used to get window region. + * @param direction UiAutomator Direction enum to indicate the swipe direction. + * + * @return true if the swipe operation is successful. + */ + fun switchToPreviousAppByQuickSwitchGesture( + wmHelper: WindowManagerStateHelper, + direction: Direction + ): Boolean { + val ratioForScreenBottom = 0.97 + val fullView = wmHelper.getWindowRegion(componentMatcher) + require(!fullView.isEmpty) { "Target $componentMatcher view not found." } + + val bound = fullView.bounds + val targetYPos = bound.bottom * ratioForScreenBottom + val endX = when (direction) { + Direction.LEFT -> bound.left + Direction.RIGHT -> bound.right + else -> { + throw IllegalStateException("Only left or right direction is allowed.") + } + } + return uiDevice.swipe( + bound.centerX(), targetYPos.toInt(), endX, targetYPos.toInt(), SWIPE_STEPS) + } + + /** + * Waits for view idel with timeout, then checkes the target object whether visible on screen. + * + * @param packageName The targe application's package name. + * @param identifier The resource id of the target object. + * @param timeout The timeout duration in milliseconds. + * + * @return true if the target object exists. + */ + @JvmOverloads + fun isTargetObjVisible( + packageName: String, + identifier: String, + timeout: Long = WAIT_TIME_MS + ): Boolean { + uiDevice.waitForIdle(timeout) + return uiDevice.hasObject(By.res(packageName, identifier)) + } + + companion object { + private const val GAME_APP_VIEW_RES = "container" + private const val WAIT_TIME_MS = 3_000L + private const val SWIPE_STEPS = 30 + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt new file mode 100644 index 000000000000..807e6729c2d0 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.parser.toFlickerComponent + +class MailAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME, + component: ComponentNameMatcher = + ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + fun openMail(rowIdx: Int) { + val rowSel = By.res(getPackage(), "mail_row_item_text") + .textEndsWith(String.format("%04d", rowIdx)) + var row: UiObject2? = null + for (i in 1..1000) { + row = uiDevice.wait(Until.findObject(rowSel), SHORT_WAIT_TIME_MS) + if (row != null) break + scrollDown() + } + require(row != null) {""} + row.click() + uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT) + } + + fun scrollDown() { + val listView = waitForMailList() + listView.scroll(Direction.DOWN, 1.0f) + } + + fun waitForMailList(): UiObject2 { + var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true) + val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT) + require(ret != null) {""} + return ret + } + + companion object { + private const val SHORT_WAIT_TIME_MS = 5000L + private const val MAIL_LIST_RES_ID = "mail_recycle_view" + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt index 354964d33ece..9b1c541da94e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt @@ -62,19 +62,23 @@ class OpenAppColdFromIcon( get() = { super.transition(this) setup { - tapl.setExpectedRotation(Surface.ROTATION_0) + if (testSpec.isTablet) { + tapl.setExpectedRotation(testSpec.startRotation) + } else { + tapl.setExpectedRotation(Surface.ROTATION_0) + } RemoveAllTasksButHomeRule.removeAllTasksButHome() this.setRotation(testSpec.startRotation) } - teardown { - testApp.exit(wmHelper) - } transitions { tapl.goHome() .switchToAllApps() .getAppIcon(testApp.launcherName) .launch(testApp.`package`) } + teardown { + testApp.exit(wmHelper) + } } /** {@inheritDoc} */ diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 54662ef2d0b5..725963bc7ca3 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -214,5 +214,34 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <service + android:name=".AssistantInteractionSessionService" + android:exported="true" + android:permission="android.permission.BIND_VOICE_INTERACTION" /> + <service + android:name=".AssistantRecognitionService" + android:exported="true" + android:label="Test Voice Interaction Service"> + <intent-filter> + <action android:name="android.speech.RecognitionService" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data + android:name="android.speech" + android:resource="@xml/recognition_service" /> + </service> + <service + android:name=".AssistantInteractionService" + android:exported="true" + android:label="Test Voice Interaction Service" + android:permission="android.permission.BIND_VOICE_INTERACTION"> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService" /> + </intent-filter> + <meta-data + android:name="android.voice_interaction" + android:resource="@xml/interaction_service" /> + </service> </application> + <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml new file mode 100644 index 000000000000..eb7f3074ebfb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:id="@+id/vis_frame" + android:layout_width="match_parent" + android:layout_height="300dp" + android:layout_gravity="bottom" + android:background="#37474F"/> +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml new file mode 100644 index 000000000000..2e661fbd3122 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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. + --> + +<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" + android:recognitionService="com.android.server.wm.flicker.testapp.AssistantRecognitionService" + android:sessionService="com.android.server.wm.flicker.testapp.AssistantInteractionSessionService" + android:supportsAssist="true" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml new file mode 100644 index 000000000000..2e124982084f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml @@ -0,0 +1,17 @@ +<!-- + ~ 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. + --> + +<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 72a02f24629f..cae3df4f9486 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -101,6 +101,10 @@ public class ActivityOptions { FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity"); + public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity"; + public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName( + FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity"); + public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp"; public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java index 1de740a083c2..d60143cdf40a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,15 +14,9 @@ * limitations under the License. */ -package com.android.systemui.shared.system; +package com.android.server.wm.flicker.testapp; -import android.annotation.UserIdInt; -import android.content.Context; +import android.service.voice.VoiceInteractionService; -public class ContextUtils { - - /** Get the user associated with this context */ - public static @UserIdInt int getUserId(Context context) { - return context.getUserId(); - } +public class AssistantInteractionService extends VoiceInteractionService { } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java new file mode 100644 index 000000000000..d2c9b37704b8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.view.View; +import android.view.Window; + +public class AssistantInteractionSession extends VoiceInteractionSession { + + public AssistantInteractionSession(Context context) { + super(context); + } + + @Override + public void onCreate() { + Window window = getWindow().getWindow(); + if (window != null) { + window.getDecorView().setBackgroundColor(Color.TRANSPARENT); + + } + View rootView = getLayoutInflater().inflate(R.layout.assistant_session, null); + setContentView(rootView); + setUiEnabled(false); + } + + @Override + public void onShow(Bundle args, int showFlags) { + setUiEnabled(true); + } + + @Override + public void onHide() { + setUiEnabled(false); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java new file mode 100644 index 000000000000..4d6125c9a5d8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.service.voice.VoiceInteractionSessionService; + +public class AssistantInteractionSessionService extends VoiceInteractionSessionService { + @Override + public VoiceInteractionSession onNewSession(Bundle args) { + return new AssistantInteractionSession(this); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/KeyguardManagerCompat.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java index c42e7e3fd216..68aae4520fe9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/KeyguardManagerCompat.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -11,22 +11,27 @@ * 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 + * limitations under the License. */ -package com.android.systemui.shared.system; +package com.android.server.wm.flicker.testapp; -import android.app.KeyguardManager; -import android.content.Context; +import android.content.Intent; +import android.speech.RecognitionService; -public class KeyguardManagerCompat { - private final KeyguardManager mKeyguardManager; +public class AssistantRecognitionService extends RecognitionService { + @Override + protected void onStartListening(Intent recognizerIntent, Callback listener) { - public KeyguardManagerCompat(Context context) { - mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); } - public boolean isDeviceLocked(int userId) { - return mKeyguardManager.isDeviceLocked(userId); + @Override + protected void onCancel(Callback listener) { + + } + + @Override + protected void onStopListening(Callback listener) { + } } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index b0ccbd1cf22f..939c7de22356 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -436,6 +436,15 @@ </intent-filter> </activity> + <activity android:name="SurfaceViewAlphaActivity" + android:label="SurfaceView/SurfaceView with Alpha" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name=".PenStylusActivity" android:label="Pen/Draw" android:exported="true"> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java new file mode 100644 index 000000000000..01fe6ae0518b --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java @@ -0,0 +1,168 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Color; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; +import android.view.SurfaceView; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +public class SurfaceViewAlphaActivity extends Activity implements Callback { + SurfaceView mSurfaceView; + + private enum ZOrder { + ABOVE, + BELOW + } + + private float mAlpha = 127f / 255f; + private ZOrder mZOrder = ZOrder.BELOW; + + + private String getAlphaText() { + return "Alpha: " + mAlpha; + } + + private void toggleZOrder() { + if (ZOrder.ABOVE.equals(mZOrder)) { + mZOrder = ZOrder.BELOW; + } else { + mZOrder = ZOrder.ABOVE; + } + } + + // Overlaps a blue view on the left, then the SurfaceView in the center, then a blue view on the + // right. + private void overlapViews(SurfaceView view, LinearLayout parent) { + float density = getResources().getDisplayMetrics().density; + int surfaceViewSize = (int) (200 * density); + int blueViewSize = (int) (surfaceViewSize * 2 / 3f); + int totalSize = (int) (surfaceViewSize * 5 / 3f); + + RelativeLayout overlapLayout = new RelativeLayout(this); + + RelativeLayout.LayoutParams leftViewLayoutParams = new RelativeLayout.LayoutParams( + blueViewSize, surfaceViewSize); + leftViewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + View leftBlueView = new View(this); + leftBlueView.setBackgroundColor(Color.BLUE); + overlapLayout.addView(leftBlueView, leftViewLayoutParams); + + RelativeLayout.LayoutParams sVLayoutParams = new RelativeLayout.LayoutParams( + surfaceViewSize, surfaceViewSize); + sVLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + overlapLayout.addView(view, sVLayoutParams); + + RelativeLayout.LayoutParams rightViewLayoutParams = new RelativeLayout.LayoutParams( + blueViewSize, surfaceViewSize); + rightViewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + + View rightBlueView = new View(this); + rightBlueView.setBackgroundColor(Color.BLUE); + overlapLayout.addView(rightBlueView, rightViewLayoutParams); + + parent.addView(overlapLayout, new LinearLayout.LayoutParams( + totalSize, surfaceViewSize)); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + mSurfaceView.setAlpha(mAlpha); + + LinearLayout content = new LinearLayout(this); + content.setOrientation(LinearLayout.VERTICAL); + + TextView alphaText = new TextView(this); + alphaText.setText(getAlphaText()); + + SeekBar alphaToggle = new SeekBar(this); + alphaToggle.setMin(0); + alphaToggle.setMax(255); + alphaToggle.setProgress(Math.round(mAlpha * 255)); + alphaToggle.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mAlpha = progress / 255f; + alphaText.setText(getAlphaText()); + mSurfaceView.setAlpha(mAlpha); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + content.addView(alphaText, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + content.addView(alphaToggle, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + Button button = new Button(this); + button.setText("Z " + mZOrder.toString()); + button.setOnClickListener(v -> { + toggleZOrder(); + mSurfaceView.setZOrderOnTop(ZOrder.ABOVE.equals(mZOrder)); + button.setText("Z " + mZOrder.toString()); + }); + + content.addView(button, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + overlapViews(mSurfaceView, content); + + setContentView(content); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Canvas canvas = holder.lockCanvas(); + canvas.drawColor(Color.RED); + holder.unlockCanvasAndPost(canvas); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } +} diff --git a/tests/TouchLatency/OWNERS b/tests/TouchLatency/OWNERS new file mode 100644 index 000000000000..2b7de2555587 --- /dev/null +++ b/tests/TouchLatency/OWNERS @@ -0,0 +1,2 @@ +include platform/frameworks/base:/graphics/java/android/graphics/OWNERS +include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file diff --git a/tests/TouchLatency/app/build.gradle b/tests/TouchLatency/app/build.gradle index 04a878896f47..f5ae6f4b4ffc 100644 --- a/tests/TouchLatency/app/build.gradle +++ b/tests/TouchLatency/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 33 buildToolsVersion '28.0.3' defaultConfig { applicationId "com.prefabulated.touchlatency" minSdkVersion 28 - targetSdkVersion 28 + targetSdkVersion 33 versionCode 1 versionName "1.0" } diff --git a/tests/TouchLatency/app/src/main/AndroidManifest.xml b/tests/TouchLatency/app/src/main/AndroidManifest.xml index 98947367bd7b..25bb5d92f846 100644 --- a/tests/TouchLatency/app/src/main/AndroidManifest.xml +++ b/tests/TouchLatency/app/src/main/AndroidManifest.xml @@ -20,16 +20,22 @@ <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:resizeableActivity="true" > <activity android:name=".TouchLatencyActivity" android:label="@string/app_name" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + + <activity android:name=".TouchLatencyActivityPresentation" + android:label="@string/app_name" + android:parentActivityName=".TouchLatencyActivity" + android:exported="true"> + </activity> </application> </manifest> diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java index a2842b6e214d..6ab3b3e6c037 100644 --- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java +++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java @@ -17,218 +17,40 @@ package com.prefabulated.touchlatency; import android.app.Activity; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; +import android.app.ActivityOptions; +import android.content.Intent; +import android.hardware.display.DisplayManager; import android.os.Bundle; +import android.os.Handler; import android.os.Trace; -import android.util.AttributeSet; -import android.util.Log; import android.view.Display; import android.view.Display.Mode; import android.view.Menu; import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; import android.view.Window; import android.view.WindowManager; -import java.math.RoundingMode; -import java.text.DecimalFormat; - -class TouchLatencyView extends View implements View.OnTouchListener { - private static final String LOG_TAG = "TouchLatency"; - private static final int BACKGROUND_COLOR = 0xFF400080; - private static final int INNER_RADIUS = 70; - private static final int BALL_DIAMETER = 200; - private static final int SEC_TO_NANOS = 1000000000; - private static final float FPS_UPDATE_THRESHOLD = 20; - private static final long BALL_VELOCITY = 420; - - public TouchLatencyView(Context context, AttributeSet attrs) { - super(context, attrs); - Trace.beginSection("TouchLatencyView constructor"); - setOnTouchListener(this); - setWillNotDraw(false); - mBluePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBluePaint.setColor(0xFF0000FF); - mBluePaint.setStyle(Paint.Style.FILL); - mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mGreenPaint.setColor(0xFF00FF00); - mGreenPaint.setStyle(Paint.Style.FILL); - mYellowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mYellowPaint.setColor(0xFFFFFF00); - mYellowPaint.setStyle(Paint.Style.FILL); - mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mRedPaint.setColor(0xFFFF0000); - mRedPaint.setStyle(Paint.Style.FILL); - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTextPaint.setColor(0xFFFFFFFF); - mTextPaint.setTextSize(100); - mTextPaint.setTextAlign(Align.RIGHT); - - mTouching = false; - - mLastDrawNano = 0; - mFps = 0; - mLastFpsUpdate = 0; - mFrameCount = 0; - - mDf = new DecimalFormat("fps: #.##"); - mDf.setRoundingMode(RoundingMode.HALF_UP); - - Trace.endSection(); - } - - @Override - public boolean onTouch(View view, MotionEvent event) { - Trace.beginSection("TouchLatencyView onTouch"); - int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { - mTouching = true; - invalidate(); - - mTouchX = event.getX(); - mTouchY = event.getY(); - } else if (action == MotionEvent.ACTION_UP) { - mTouching = false; - invalidate(); - } - Trace.endSection(); - return true; - } - - private void drawTouch(Canvas canvas) { - Trace.beginSection("TouchLatencyView drawTouch"); - - try { - if (!mTouching) { - Log.d(LOG_TAG, "Filling background"); - canvas.drawColor(BACKGROUND_COLOR); - return; - } - - float deltaX = (mTouchX - mLastDrawnX); - float deltaY = (mTouchY - mLastDrawnY); - float scaleFactor = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) * 1.5f; - - mLastDrawnX = mTouchX; - mLastDrawnY = mTouchY; - - canvas.drawColor(BACKGROUND_COLOR); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 3 * scaleFactor, mRedPaint); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 2 * scaleFactor, mYellowPaint); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + scaleFactor, mGreenPaint); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS, mBluePaint); - } finally { - Trace.endSection(); - } - } - - private Paint getBallColor() { - if (mFps > 75) - return mGreenPaint; - else if (mFps > 45) - return mYellowPaint; - else - return mRedPaint; - } - - private void drawBall(Canvas canvas) { - Trace.beginSection("TouchLatencyView drawBall"); - int width = canvas.getWidth(); - int height = canvas.getHeight(); - float fps = 0f; - - long t = System.nanoTime(); - long tDiff = t - mLastDrawNano; - mLastDrawNano = t; - mFrameCount++; - - if (tDiff < SEC_TO_NANOS) { - fps = 1f * SEC_TO_NANOS / tDiff; - } - - long fDiff = t - mLastFpsUpdate; - if (Math.abs(mFps - fps) > FPS_UPDATE_THRESHOLD) { - mFps = fps; - mLastFpsUpdate = t; - mFrameCount = 0; - } else if (fDiff > SEC_TO_NANOS) { - mFps = 1f * mFrameCount * SEC_TO_NANOS / fDiff; - mLastFpsUpdate = t; - mFrameCount = 0; - } - - final long pos = t * BALL_VELOCITY / SEC_TO_NANOS; - final long xMax = width - BALL_DIAMETER; - final long yMax = height - BALL_DIAMETER; - long xOffset = pos % xMax; - long yOffset = pos % yMax; - - float left, right, top, bottom; - - if (((pos / xMax) & 1) == 0) { - left = xMax - xOffset; - } else { - left = xOffset; +public class TouchLatencyActivity extends Activity { + private Mode mDisplayModes[]; + private int mCurrentModeIndex; + private DisplayManager mDisplayManager; + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int i) { + invalidateOptionsMenu(); } - right = left + BALL_DIAMETER; - if (((pos / yMax) & 1) == 0) { - top = yMax - yOffset; - } else { - top = yOffset; + @Override + public void onDisplayRemoved(int i) { + invalidateOptionsMenu(); } - bottom = top + BALL_DIAMETER; - - // Draw the ball - canvas.drawColor(BACKGROUND_COLOR); - canvas.drawOval(left, top, right, bottom, getBallColor()); - canvas.drawText(mDf.format(mFps), width, 100, mTextPaint); - invalidate(); - Trace.endSection(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - Trace.beginSection("TouchLatencyView onDraw"); - if (mMode == 0) { - drawTouch(canvas); - } else { - drawBall(canvas); + @Override + public void onDisplayChanged(int i) { + invalidateOptionsMenu(); } - Trace.endSection(); - } - - public void changeMode(MenuItem item) { - Trace.beginSection("TouchLatencyView changeMode"); - final int NUM_MODES = 2; - final String modes[] = {"Touch", "Ball"}; - mMode = (mMode + 1) % NUM_MODES; - invalidate(); - item.setTitle(modes[mMode]); - Trace.endSection(); - } - - private final Paint mBluePaint, mGreenPaint, mYellowPaint, mRedPaint, mTextPaint; - private int mMode; - - private boolean mTouching; - private float mTouchX, mTouchY; - private float mLastDrawnX, mLastDrawnY; - - private long mLastDrawNano, mLastFpsUpdate, mFrameCount; - private float mFps; - private DecimalFormat mDf; -} - -public class TouchLatencyActivity extends Activity { - private Mode mDisplayModes[]; - private int mCurrentModeIndex; + }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -236,9 +58,9 @@ public class TouchLatencyActivity extends Activity { Trace.beginSection("TouchLatencyActivity onCreate"); setContentView(R.layout.activity_touch_latency); - mTouchView = findViewById(R.id.canvasView); + configureDisplayListener(); WindowManager wm = getWindowManager(); Display display = wm.getDefaultDisplay(); mDisplayModes = display.getSupportedModes(); @@ -250,11 +72,9 @@ public class TouchLatencyActivity extends Activity { break; } } - Trace.endSection(); } - @Override public boolean onCreateOptionsMenu(Menu menu) { Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu"); @@ -265,17 +85,26 @@ public class TouchLatencyActivity extends Activity { Mode currentMode = getWindowManager().getDefaultDisplay().getMode(); updateDisplayMode(menuItem, currentMode); } + updateMultiDisplayMenu(menu.findItem(R.id.multi_display)); Trace.endSection(); return true; } - private void updateDisplayMode(MenuItem menuItem, Mode displayMode) { int fps = (int) displayMode.getRefreshRate(); menuItem.setTitle(fps + "hz"); menuItem.setVisible(true); } + private void updateMultiDisplayMenu(MenuItem item) { + item.setVisible(mDisplayManager.getDisplays().length > 1); + } + + private void configureDisplayListener() { + mDisplayManager = getSystemService(DisplayManager.class); + mDisplayManager.registerDisplayListener(mDisplayListener, new Handler()); + } + public void changeDisplayMode(MenuItem item) { Window w = getWindow(); WindowManager.LayoutParams params = w.getAttributes(); @@ -299,6 +128,19 @@ public class TouchLatencyActivity extends Activity { mCurrentModeIndex = modeIndex; } + private void changeMultipleDisplays() { + Intent intent = new Intent(this, TouchLatencyActivityPresentation.class); + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK); + ActivityOptions options = ActivityOptions.makeBasic(); + for (int i = 1; i < mDisplayManager.getDisplays().length; ++i) { + // We assume the first display is already displaying the TouchLatencyActivity + int displayId = mDisplayManager.getDisplays()[i].getDisplayId(); + options.setLaunchDisplayId(displayId); + intent.putExtra(TouchLatencyActivityPresentation.DISPLAY_ID, displayId); + startActivity(intent, options.toBundle()); + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -309,15 +151,32 @@ public class TouchLatencyActivity extends Activity { int id = item.getItemId(); //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - mTouchView.changeMode(item); - } else if (id == R.id.display_mode) { - changeDisplayMode(item); + switch (id) { + case R.id.action_settings: { + mTouchView.changeMode(item); + break; + } + case R.id.display_mode: { + changeDisplayMode(item); + break; + } + case R.id.multi_display: { + changeMultipleDisplays(); + break; + } } Trace.endSection(); return super.onOptionsItemSelected(item); } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mDisplayManager != null) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + } + } + private TouchLatencyView mTouchView; } diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivityPresentation.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivityPresentation.java new file mode 100644 index 000000000000..2602e6b34395 --- /dev/null +++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivityPresentation.java @@ -0,0 +1,128 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.prefabulated.touchlatency; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Trace; +import android.view.Display; +import android.view.Display.Mode; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Window; +import android.view.WindowManager; + +public class TouchLatencyActivityPresentation extends Activity { + public static final String DISPLAY_ID = "DISPLAY_ID"; + private Mode[] mDisplayModes; + private int mCurrentModeIndex; + private int mDisplayId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getIntent().hasExtra(DISPLAY_ID)) { + mDisplayId = (int) getIntent().getExtras().get(DISPLAY_ID); + } + Trace.beginSection( + "TouchLatencyActivityPresentation::DisplayId::" + mDisplayId + " onCreate"); + setContentView(R.layout.activity_touch_latency); + + mTouchView = findViewById(R.id.canvasView); + + WindowManager wm = getWindowManager(); + Display display = wm.getDefaultDisplay(); + mDisplayModes = display.getSupportedModes(); + Mode currentMode = getWindowManager().getDefaultDisplay().getMode(); + + for (int i = 0; i < mDisplayModes.length; i++) { + if (currentMode.getModeId() == mDisplayModes[i].getModeId()) { + mCurrentModeIndex = i; + break; + } + } + Trace.endSection(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + Trace.beginSection( + "TouchLatencyActivityPresentation::DisplayId:: " + + mDisplayId + " onCreateOptionsMenu"); + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_touch_latency, menu); + if (mDisplayModes.length > 1) { + MenuItem menuItem = menu.findItem(R.id.display_mode); + Mode currentMode = getWindowManager().getDefaultDisplay().getMode(); + updateDisplayMode(menuItem, currentMode); + } + Trace.endSection(); + return true; + } + + private void updateDisplayMode(MenuItem menuItem, Mode displayMode) { + int fps = (int) displayMode.getRefreshRate(); + menuItem.setTitle(fps + "hz"); + menuItem.setVisible(true); + } + + public void changeDisplayMode(MenuItem item) { + Window w = getWindow(); + WindowManager.LayoutParams params = w.getAttributes(); + + int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length; + params.preferredDisplayModeId = mDisplayModes[modeIndex].getModeId(); + w.setAttributes(params); + + updateDisplayMode(item, mDisplayModes[modeIndex]); + mCurrentModeIndex = modeIndex; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Trace.beginSection( + "TouchLatencyActivityPresentation::DisplayId::" + + mDisplayId + " onOptionsItemSelected"); + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + switch (id) { + case R.id.action_settings: { + mTouchView.changeMode(item); + break; + } + case R.id.display_mode: { + changeDisplayMode(item); + break; + } + } + + Trace.endSection(); + return super.onOptionsItemSelected(item); + } + + private TouchLatencyView mTouchView; +} diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyView.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyView.java new file mode 100644 index 000000000000..0803e8e8510f --- /dev/null +++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyView.java @@ -0,0 +1,219 @@ +/* + * 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.prefabulated.touchlatency; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Trace; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; + +import java.math.RoundingMode; +import java.text.DecimalFormat; + +class TouchLatencyView extends View implements View.OnTouchListener { + private static final String LOG_TAG = "TouchLatency"; + private static final int BACKGROUND_COLOR = 0xFF400080; + private static final int INNER_RADIUS = 70; + private static final int BALL_DIAMETER = 200; + private static final int SEC_TO_NANOS = 1000000000; + private static final float FPS_UPDATE_THRESHOLD = 20; + private static final long BALL_VELOCITY = 420; + + public TouchLatencyView(Context context, AttributeSet attrs) { + super(context, attrs); + Trace.beginSection("TouchLatencyView constructor"); + setOnTouchListener(this); + setWillNotDraw(false); + mBluePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBluePaint.setColor(0xFF0000FF); + mBluePaint.setStyle(Paint.Style.FILL); + mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGreenPaint.setColor(0xFF00FF00); + mGreenPaint.setStyle(Paint.Style.FILL); + mYellowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mYellowPaint.setColor(0xFFFFFF00); + mYellowPaint.setStyle(Paint.Style.FILL); + mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRedPaint.setColor(0xFFFF0000); + mRedPaint.setStyle(Paint.Style.FILL); + mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setColor(0xFFFFFFFF); + mTextPaint.setTextSize(100); + mTextPaint.setTextAlign(Paint.Align.RIGHT); + + mTouching = false; + + mLastDrawNano = 0; + mFps = 0; + mLastFpsUpdate = 0; + mFrameCount = 0; + + mDf = new DecimalFormat("fps: #.##"); + mDf.setRoundingMode(RoundingMode.HALF_UP); + + Trace.endSection(); + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + Trace.beginSection("TouchLatencyView onTouch"); + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { + mTouching = true; + invalidate(); + + mTouchX = event.getX(); + mTouchY = event.getY(); + } else if (action == MotionEvent.ACTION_UP) { + mTouching = false; + invalidate(); + } + Trace.endSection(); + return true; + } + + private void drawTouch(Canvas canvas) { + Trace.beginSection("TouchLatencyView drawTouch"); + + try { + if (!mTouching) { + Log.d(LOG_TAG, "Filling background"); + canvas.drawColor(BACKGROUND_COLOR); + return; + } + + float deltaX = (mTouchX - mLastDrawnX); + float deltaY = (mTouchY - mLastDrawnY); + float scaleFactor = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) * 1.5f; + + mLastDrawnX = mTouchX; + mLastDrawnY = mTouchY; + + canvas.drawColor(BACKGROUND_COLOR); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 3 * scaleFactor, mRedPaint); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 2 * scaleFactor, mYellowPaint); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + scaleFactor, mGreenPaint); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS, mBluePaint); + } finally { + Trace.endSection(); + } + } + + private Paint getBallColor() { + if (mFps > 75) { + return mGreenPaint; + } else if (mFps > 45) { + return mYellowPaint; + } else + return mRedPaint; + } + + private void drawBall(Canvas canvas) { + Trace.beginSection("TouchLatencyView drawBall"); + int width = canvas.getWidth(); + int height = canvas.getHeight(); + float fps = 0f; + + long t = System.nanoTime(); + long tDiff = t - mLastDrawNano; + mLastDrawNano = t; + mFrameCount++; + + if (tDiff < SEC_TO_NANOS) { + fps = 1f * SEC_TO_NANOS / tDiff; + } + + long fDiff = t - mLastFpsUpdate; + if (Math.abs(mFps - fps) > FPS_UPDATE_THRESHOLD) { + mFps = fps; + mLastFpsUpdate = t; + mFrameCount = 0; + } else if (fDiff > SEC_TO_NANOS) { + mFps = 1f * mFrameCount * SEC_TO_NANOS / fDiff; + mLastFpsUpdate = t; + mFrameCount = 0; + } + + final long pos = t * BALL_VELOCITY / SEC_TO_NANOS; + final long xMax = width - BALL_DIAMETER; + final long yMax = height - BALL_DIAMETER; + long xOffset = pos % xMax; + long yOffset = pos % yMax; + + float left, right, top, bottom; + + if (((pos / xMax) & 1) == 0) { + left = xMax - xOffset; + } else { + left = xOffset; + } + right = left + BALL_DIAMETER; + + if (((pos / yMax) & 1) == 0) { + top = yMax - yOffset; + } else { + top = yOffset; + } + bottom = top + BALL_DIAMETER; + + // Draw the ball + canvas.drawColor(BACKGROUND_COLOR); + canvas.drawOval(left, top, right, bottom, getBallColor()); + canvas.drawText(mDf.format(mFps), width, 100, mTextPaint); + + invalidate(); + Trace.endSection(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Trace.beginSection("TouchLatencyView onDraw"); + if (mMode == 0) { + drawTouch(canvas); + } else { + drawBall(canvas); + } + Trace.endSection(); + } + + public void changeMode(MenuItem item) { + Trace.beginSection("TouchLatencyView changeMode"); + final int NUM_MODES = 2; + final String modes[] = {"Touch", "Ball"}; + mMode = (mMode + 1) % NUM_MODES; + invalidate(); + item.setTitle(modes[mMode]); + Trace.endSection(); + } + + private final Paint mBluePaint, mGreenPaint, mYellowPaint, mRedPaint, mTextPaint; + private int mMode; + + private boolean mTouching; + private float mTouchX, mTouchY; + private float mLastDrawnX, mLastDrawnY; + + private long mLastDrawNano, mLastFpsUpdate, mFrameCount; + private float mFps; + private DecimalFormat mDf; +} diff --git a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml index 52be91900ae8..abc7fd5d6bb2 100644 --- a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml +++ b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml @@ -25,4 +25,10 @@ android:showAsAction="ifRoom" android:title="@string/display_mode" android:visible="false"/> + + <item + android:id="@+id/multi_display" + android:showAsAction="ifRoom" + android:title="@string/multi_display" + android:visible="false"/> </menu> diff --git a/tests/TouchLatency/app/src/main/res/values/strings.xml b/tests/TouchLatency/app/src/main/res/values/strings.xml index 771992c8e5d3..5ee86d8bd8bf 100644 --- a/tests/TouchLatency/app/src/main/res/values/strings.xml +++ b/tests/TouchLatency/app/src/main/res/values/strings.xml @@ -18,4 +18,5 @@ <string name="mode">Touch</string> <string name="display_mode">Mode</string> + <string name="multi_display">multi-display</string> </resources> diff --git a/tests/TouchLatency/build.gradle b/tests/TouchLatency/build.gradle index 03abe82a4359..381e55e92c7d 100644 --- a/tests/TouchLatency/build.gradle +++ b/tests/TouchLatency/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:4.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties index 2d80b69a7665..4d9ca1649142 100644 --- a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties +++ b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists |