diff options
552 files changed, 11900 insertions, 5720 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index c80f2eace19a..b3e8ea8fabf7 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -55,6 +55,7 @@ aconfig_srcjars = [ ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", + ":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.media.tv.flags-aconfig-java{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", @@ -584,6 +585,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Pinner Service +aconfig_declarations { + name: "com.android.server.flags.pinner-aconfig", + package: "com.android.server.flags", + srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"], +} + +java_aconfig_library { + name: "com.android.server.flags.pinner-aconfig-java", + aconfig_declarations: "com.android.server.flags.pinner-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Voice aconfig_declarations { name: "android.service.voice.flags-aconfig", diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 158d914575c6..6383ed873e59 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -32,6 +32,7 @@ import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.BroadcastOptions; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; @@ -41,6 +42,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.res.Resources; +import android.database.ContentObserver; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -81,6 +83,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.WearModeManagerInternal; import android.provider.DeviceConfig; +import android.provider.Settings; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; @@ -109,6 +112,7 @@ import com.android.server.deviceidle.DeviceIdleConstraintTracker; import com.android.server.deviceidle.IDeviceIdleConstraint; import com.android.server.deviceidle.TvConstraintController; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.utils.UserSettingDeviceConfigMediator; import com.android.server.wm.ActivityTaskManagerInternal; import org.xmlpull.v1.XmlPullParser; @@ -1020,7 +1024,8 @@ public class DeviceIdleController extends SystemService * global Settings. Any access to this class or its fields should be done while * holding the DeviceIdleController lock. */ - public final class Constants implements DeviceConfig.OnPropertiesChangedListener { + public final class Constants extends ContentObserver + implements DeviceConfig.OnPropertiesChangedListener { // Key names stored in the settings value. private static final String KEY_FLEX_TIME_SHORT = "flex_time_short"; private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = @@ -1102,7 +1107,7 @@ public class DeviceIdleController extends SystemService 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); + (60 * 1000L) / (!COMPRESS_TIME ? 1 : 10); private long mDefaultSensingTimeout = !COMPRESS_TIME ? 4 * 60 * 1000L : 60 * 1000L; private long mDefaultLocatingTimeout = @@ -1115,7 +1120,7 @@ public class DeviceIdleController extends SystemService 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); + (60 * 1000L) / (!COMPRESS_TIME ? 1 : 10); private long mDefaultIdlePendingTimeout = !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L; private long mDefaultMaxIdlePendingTimeout = @@ -1396,6 +1401,7 @@ public class DeviceIdleController extends SystemService /** * 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 = mDefaultNotificationAllowlistDurationMs; @@ -1413,9 +1419,14 @@ public class DeviceIdleController extends SystemService */ public boolean USE_MODE_MANAGER = mDefaultUseModeManager; + private final ContentResolver mResolver; private final boolean mSmallBatteryDevice; + private final UserSettingDeviceConfigMediator mUserSettingDeviceConfigMediator = + new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(','); - public Constants() { + public Constants(Handler handler, ContentResolver resolver) { + super(handler); + mResolver = resolver; initDefault(); mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice(); if (mSmallBatteryDevice) { @@ -1424,8 +1435,14 @@ public class DeviceIdleController extends SystemService } DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DEVICE_IDLE, AppSchedulingModuleThread.getExecutor(), this); + mResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS), + false, this); // Load all the constants. - onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE)); + updateSettingsConstantLocked(); + mUserSettingDeviceConfigMediator.setDeviceConfigProperties( + DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE)); + updateConstantsLocked(); } private void initDefault() { @@ -1574,188 +1591,166 @@ public class DeviceIdleController extends SystemService return (!COMPRESS_TIME || defTimeout < compTimeout) ? defTimeout : compTimeout; } + @Override + public void onChange(boolean selfChange, Uri uri) { + synchronized (DeviceIdleController.this) { + updateSettingsConstantLocked(); + updateConstantsLocked(); + } + } + + private void updateSettingsConstantLocked() { + try { + mUserSettingDeviceConfigMediator.setSettingsString( + Settings.Global.getString(mResolver, + Settings.Global.DEVICE_IDLE_CONSTANTS)); + } catch (IllegalArgumentException e) { + // Failed to parse the settings string, log this and move on with previous values. + Slog.e(TAG, "Bad device idle settings", e); + } + } @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { synchronized (DeviceIdleController.this) { - for (String name : properties.getKeyset()) { - if (name == null) { - continue; - } - switch (name) { - case KEY_FLEX_TIME_SHORT: - FLEX_TIME_SHORT = properties.getLong( - 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, - mDefaultLightIdleAfterInactiveTimeout); - break; - case KEY_LIGHT_IDLE_TIMEOUT: - LIGHT_IDLE_TIMEOUT = properties.getLong( - KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout); - break; - case KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX: - LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = properties.getLong( - KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX, - mDefaultLightIdleTimeoutInitialFlex); - break; - case KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX: - LIGHT_IDLE_TIMEOUT_MAX_FLEX = properties.getLong( - KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX, - mDefaultLightIdleTimeoutMaxFlex); - break; - case KEY_LIGHT_IDLE_FACTOR: - LIGHT_IDLE_FACTOR = Math.max(1, properties.getFloat( - KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor)); - break; - case KEY_LIGHT_IDLE_INCREASE_LINEARLY: - LIGHT_IDLE_INCREASE_LINEARLY = properties.getBoolean( - KEY_LIGHT_IDLE_INCREASE_LINEARLY, - mDefaultLightIdleIncreaseLinearly); - break; - case KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS: - LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS = properties.getLong( - KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS, - mDefaultLightIdleLinearIncreaseFactorMs); - break; - case KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS: - LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS = properties.getLong( - KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS, - mDefaultLightIdleFlexLinearIncreaseFactorMs); - break; - case KEY_LIGHT_MAX_IDLE_TIMEOUT: - LIGHT_MAX_IDLE_TIMEOUT = properties.getLong( - 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, - mDefaultLightIdleMaintenanceMinBudget); - break; - case KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET: - LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = properties.getLong( - KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, - mDefaultLightIdleMaintenanceMaxBudget); - break; - case KEY_MIN_LIGHT_MAINTENANCE_TIME: - MIN_LIGHT_MAINTENANCE_TIME = properties.getLong( - KEY_MIN_LIGHT_MAINTENANCE_TIME, - mDefaultMinLightMaintenanceTime); - break; - case KEY_MIN_DEEP_MAINTENANCE_TIME: - MIN_DEEP_MAINTENANCE_TIME = properties.getLong( - KEY_MIN_DEEP_MAINTENANCE_TIME, - mDefaultMinDeepMaintenanceTime); - break; - case KEY_INACTIVE_TIMEOUT: - final long defaultInactiveTimeout = mSmallBatteryDevice - ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY - : mDefaultInactiveTimeout; - INACTIVE_TIMEOUT = properties.getLong( - KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout); - break; - case KEY_SENSING_TIMEOUT: - SENSING_TIMEOUT = properties.getLong( - KEY_SENSING_TIMEOUT, mDefaultSensingTimeout); - break; - case KEY_LOCATING_TIMEOUT: - LOCATING_TIMEOUT = properties.getLong( - KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout); - break; - case KEY_LOCATION_ACCURACY: - LOCATION_ACCURACY = properties.getFloat( - KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy); - break; - case KEY_MOTION_INACTIVE_TIMEOUT: - MOTION_INACTIVE_TIMEOUT = properties.getLong( - KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout); - break; - case KEY_MOTION_INACTIVE_TIMEOUT_FLEX: - MOTION_INACTIVE_TIMEOUT_FLEX = properties.getLong( - KEY_MOTION_INACTIVE_TIMEOUT_FLEX, - mDefaultMotionInactiveTimeoutFlex); - break; - case KEY_IDLE_AFTER_INACTIVE_TIMEOUT: - final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice - ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY - : 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, mDefaultIdlePendingTimeout); - break; - case KEY_MAX_IDLE_PENDING_TIMEOUT: - MAX_IDLE_PENDING_TIMEOUT = properties.getLong( - KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout); - break; - case KEY_IDLE_PENDING_FACTOR: - IDLE_PENDING_FACTOR = properties.getFloat( - KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor); - break; - case KEY_QUICK_DOZE_DELAY_TIMEOUT: - QUICK_DOZE_DELAY_TIMEOUT = properties.getLong( - KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout); - break; - case KEY_IDLE_TIMEOUT: - IDLE_TIMEOUT = properties.getLong( - KEY_IDLE_TIMEOUT, mDefaultIdleTimeout); - break; - case KEY_MAX_IDLE_TIMEOUT: - MAX_IDLE_TIMEOUT = properties.getLong( - KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout); - break; - case KEY_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, 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, - 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, - 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, - mDefaultSmsTempAppAllowlistDurationMs); - break; - case KEY_NOTIFICATION_ALLOWLIST_DURATION_MS: - NOTIFICATION_ALLOWLIST_DURATION_MS = properties.getLong( - KEY_NOTIFICATION_ALLOWLIST_DURATION_MS, - mDefaultNotificationAllowlistDurationMs); - break; - case KEY_WAIT_FOR_UNLOCK: - WAIT_FOR_UNLOCK = properties.getBoolean( - KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock); - break; - case KEY_USE_WINDOW_ALARMS: - USE_WINDOW_ALARMS = properties.getBoolean( - KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms); - break; - case KEY_USE_MODE_MANAGER: - USE_MODE_MANAGER = properties.getBoolean( - KEY_USE_MODE_MANAGER, mDefaultUseModeManager); - break; - default: - Slog.e(TAG, "Unknown configuration key: " + name); - break; - } - } + mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties); + updateConstantsLocked(); } } + private void updateConstantsLocked() { + if (mSmallBatteryDevice) return; + FLEX_TIME_SHORT = mUserSettingDeviceConfigMediator.getLong( + KEY_FLEX_TIME_SHORT, mDefaultFlexTimeShort); + + LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, + mDefaultLightIdleAfterInactiveTimeout); + + LIGHT_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout); + + LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX, + mDefaultLightIdleTimeoutInitialFlex); + + LIGHT_IDLE_TIMEOUT_MAX_FLEX = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX, + mDefaultLightIdleTimeoutMaxFlex); + + LIGHT_IDLE_FACTOR = Math.max(1, mUserSettingDeviceConfigMediator.getFloat( + KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor)); + + LIGHT_IDLE_INCREASE_LINEARLY = mUserSettingDeviceConfigMediator.getBoolean( + KEY_LIGHT_IDLE_INCREASE_LINEARLY, + mDefaultLightIdleIncreaseLinearly); + + LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS, + mDefaultLightIdleLinearIncreaseFactorMs); + + LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS, + mDefaultLightIdleFlexLinearIncreaseFactorMs); + + LIGHT_MAX_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_MAX_IDLE_TIMEOUT, mDefaultLightMaxIdleTimeout); + + LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, + mDefaultLightIdleMaintenanceMinBudget); + + LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mUserSettingDeviceConfigMediator.getLong( + KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, + mDefaultLightIdleMaintenanceMaxBudget); + + MIN_LIGHT_MAINTENANCE_TIME = mUserSettingDeviceConfigMediator.getLong( + KEY_MIN_LIGHT_MAINTENANCE_TIME, + mDefaultMinLightMaintenanceTime); + + MIN_DEEP_MAINTENANCE_TIME = mUserSettingDeviceConfigMediator.getLong( + KEY_MIN_DEEP_MAINTENANCE_TIME, + mDefaultMinDeepMaintenanceTime); + + final long defaultInactiveTimeout = mSmallBatteryDevice + ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY + : mDefaultInactiveTimeout; + INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout); + + SENSING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_SENSING_TIMEOUT, mDefaultSensingTimeout); + + LOCATING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout); + + LOCATION_ACCURACY = mUserSettingDeviceConfigMediator.getFloat( + KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy); + + MOTION_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout); + + MOTION_INACTIVE_TIMEOUT_FLEX = mUserSettingDeviceConfigMediator.getLong( + KEY_MOTION_INACTIVE_TIMEOUT_FLEX, + mDefaultMotionInactiveTimeoutFlex); + + final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice + ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY + : mDefaultIdleAfterInactiveTimeout; + IDLE_AFTER_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_IDLE_AFTER_INACTIVE_TIMEOUT, + defaultIdleAfterInactiveTimeout); + + IDLE_PENDING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_IDLE_PENDING_TIMEOUT, mDefaultIdlePendingTimeout); + + MAX_IDLE_PENDING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout); + + IDLE_PENDING_FACTOR = mUserSettingDeviceConfigMediator.getFloat( + KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor); + + QUICK_DOZE_DELAY_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout); + + IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_IDLE_TIMEOUT, mDefaultIdleTimeout); + + MAX_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong( + KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout); + + IDLE_FACTOR = mUserSettingDeviceConfigMediator.getFloat(KEY_IDLE_FACTOR, + mDefaultIdleFactor); + + MIN_TIME_TO_ALARM = mUserSettingDeviceConfigMediator.getLong( + KEY_MIN_TIME_TO_ALARM, mDefaultMinTimeToAlarm); + + MAX_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong( + KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS, + mDefaultMaxTempAppAllowlistDurationMs); + + MMS_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong( + KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS, + mDefaultMmsTempAppAllowlistDurationMs); + + SMS_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong( + KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS, + mDefaultSmsTempAppAllowlistDurationMs); + + NOTIFICATION_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong( + KEY_NOTIFICATION_ALLOWLIST_DURATION_MS, + mDefaultNotificationAllowlistDurationMs); + + WAIT_FOR_UNLOCK = mUserSettingDeviceConfigMediator.getBoolean( + KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock); + + USE_WINDOW_ALARMS = mUserSettingDeviceConfigMediator.getBoolean( + KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms); + + USE_MODE_MANAGER = mUserSettingDeviceConfigMediator.getBoolean( + KEY_USE_MODE_MANAGER, mDefaultUseModeManager); + } + void dump(PrintWriter pw) { pw.println(" Settings:"); @@ -2490,9 +2485,10 @@ public class DeviceIdleController extends SystemService return mConnectivityManager; } - Constants getConstants(DeviceIdleController controller) { + Constants getConstants(DeviceIdleController controller, Handler handler, + ContentResolver resolver) { if (mConstants == null) { - mConstants = controller.new Constants(); + mConstants = controller.new Constants(handler, resolver); } return mConstants; } @@ -2650,7 +2646,7 @@ public class DeviceIdleController extends SystemService } } - mConstants = mInjector.getConstants(this); + mConstants = mInjector.getConstants(this, mHandler, getContext().getContentResolver()); readConfigFileLocked(); updateWhitelistAppIdsLocked(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index d2150b80761f..8381d1a322e0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -109,7 +109,6 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; -import android.util.KeyValueListParser; import android.util.Slog; import android.util.SparseArray; @@ -154,7 +153,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedConsumptionLimit; private long mMaxSatiatedConsumptionLimit; - private final KeyValueListParser mParser = new KeyValueListParser(','); private final Injector mInjector; private final SparseArray<Action> mActions = new SparseArray<>(); @@ -241,35 +239,36 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { mRewards.clear(); try { - mParser.setString(policyValuesString); + mUserSettingDeviceConfigMediator.setSettingsString(policyValuesString); + mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties); } catch (IllegalArgumentException e) { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceOther = getConstantAsCake( KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); - mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake( KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES, mMinSatiatedBalanceOther); - mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceExempted = getConstantAsCake( KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, mMinSatiatedBalanceHeadlessSystemApp); - mMaxSatiatedBalance = getConstantAsCake(mParser, properties, + mMaxSatiatedBalance = getConstantAsCake( KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES, Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); - mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + mMinSatiatedConsumptionLimit = getConstantAsCake( KEY_AM_MIN_CONSUMPTION_LIMIT, DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES, arcToCake(1)); - mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + mInitialSatiatedConsumptionLimit = getConstantAsCake( KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, mMinSatiatedConsumptionLimit); - mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + mMaxSatiatedConsumptionLimit = getConstantAsCake( KEY_AM_MAX_CONSUMPTION_LIMIT, DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES, mInitialSatiatedConsumptionLimit); - final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties, + final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES); @@ -279,49 +278,49 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { // run out of credits, and not when the system has run out of stock. mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES), exactAllowWhileIdleWakeupBasePrice, /* respectsStockLimit */ false)); mActions.put(ACTION_ALARM_WAKEUP_EXACT, new Action(ACTION_ALARM_WAKEUP_EXACT, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES), /* respectsStockLimit */ false)); final long inexactAllowWhileIdleWakeupBasePrice = - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES), inexactAllowWhileIdleWakeupBasePrice, /* respectsStockLimit */ false)); mActions.put(ACTION_ALARM_WAKEUP_INEXACT, new Action(ACTION_ALARM_WAKEUP_INEXACT, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES), /* respectsStockLimit */ false)); - final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, + final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES); mActions.put(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, new Action(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES), exactAllowWhileIdleNonWakeupBasePrice, @@ -329,18 +328,18 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { mActions.put(ACTION_ALARM_NONWAKEUP_EXACT, new Action(ACTION_ALARM_NONWAKEUP_EXACT, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES), /* respectsStockLimit */ false)); - final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties, + final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES); - final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(mParser, properties, + final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake( KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES); mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE, @@ -350,72 +349,72 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT, new Action(ACTION_ALARM_NONWAKEUP_INEXACT, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP, DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES))); mActions.put(ACTION_ALARM_CLOCK, new Action(ACTION_ALARM_CLOCK, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP, DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE, DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES), /* respectsStockLimit */ false)); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_TOP_ACTIVITY_INSTANT, DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_TOP_ACTIVITY_ONGOING, DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_TOP_ACTIVITY_MAX, DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT, DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING, DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_NOTIFICATION_SEEN_MAX, DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_INTERACTION, new Reward(REWARD_NOTIFICATION_INTERACTION, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT, DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING, DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX, DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT, DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING, DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_WIDGET_INTERACTION_MAX, DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_OTHER_USER_INTERACTION, new Reward(REWARD_OTHER_USER_INTERACTION, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT, DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING, DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX, DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES))); } 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 a4043dd8ba78..61096b9fa179 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -33,9 +33,9 @@ import android.content.ContentResolver; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; -import android.util.KeyValueListParser; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.utils.UserSettingDeviceConfigMediator; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -197,10 +197,17 @@ public abstract class EconomicPolicy { } protected final InternalResourceService mIrs; + protected final UserSettingDeviceConfigMediator mUserSettingDeviceConfigMediator; private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS]; EconomicPolicy(@NonNull InternalResourceService irs) { mIrs = irs; + // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig + // config can cause issues since the scales may be different, so use one or the other. + // If user settings exist, then just stick with the Settings constants, even if there + // are invalid values. + mUserSettingDeviceConfigMediator = + new UserSettingDeviceConfigMediator.SettingsOverridesAllMediator(','); for (int mId : getCostModifiers()) { initModifier(mId, irs); } @@ -464,28 +471,14 @@ public abstract class EconomicPolicy { return "UNKNOWN_REWARD:" + Integer.toHexString(eventId); } - protected long getConstantAsCake(@NonNull KeyValueListParser parser, - @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) { - return getConstantAsCake(parser, properties, key, defaultValCake, 0); + protected long getConstantAsCake(String key, long defaultValCake) { + return getConstantAsCake(key, defaultValCake, 0); } - protected long getConstantAsCake(@NonNull KeyValueListParser parser, - @Nullable DeviceConfig.Properties properties, String key, long defaultValCake, - long minValCake) { - // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig - // config can cause issues since the scales may be different, so use one or the other. - if (parser.size() > 0) { - // User settings take precedence. Just stick with the Settings constants, even if there - // are invalid values. It's not worth the time to evaluate all the key/value pairs to - // make sure there are valid ones before deciding. - return Math.max(minValCake, - parseCreditValue(parser.getString(key, null), defaultValCake)); - } - if (properties != null) { - return Math.max(minValCake, - parseCreditValue(properties.getString(key, null), defaultValCake)); - } - return Math.max(minValCake, defaultValCake); + protected long getConstantAsCake(String key, long defaultValCake, long minValCake) { + return Math.max(minValCake, + parseCreditValue( + mUserSettingDeviceConfigMediator.getString(key, null), defaultValCake)); } @VisibleForTesting 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 91a291fe20db..69e57365b6e0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -127,7 +127,6 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; -import android.util.KeyValueListParser; import android.util.Slog; import android.util.SparseArray; @@ -168,7 +167,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedConsumptionLimit; private long mMaxSatiatedConsumptionLimit; - private final KeyValueListParser mParser = new KeyValueListParser(','); private final Injector mInjector; private final SparseArray<Action> mActions = new SparseArray<>(); @@ -274,176 +272,177 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { mRewards.clear(); try { - mParser.setString(policyValuesString); + mUserSettingDeviceConfigMediator.setSettingsString(policyValuesString); + mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties); } catch (IllegalArgumentException e) { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceOther = getConstantAsCake( KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); - mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake( KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES, mMinSatiatedBalanceOther); - mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceExempted = getConstantAsCake( KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, mMinSatiatedBalanceHeadlessSystemApp); - mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties, + mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake( KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER, DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES); - mMaxSatiatedBalance = getConstantAsCake(mParser, properties, + mMaxSatiatedBalance = getConstantAsCake( KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); - mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + mMinSatiatedConsumptionLimit = getConstantAsCake( KEY_JS_MIN_CONSUMPTION_LIMIT, DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES, arcToCake(1)); - mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + mInitialSatiatedConsumptionLimit = getConstantAsCake( KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, mMinSatiatedConsumptionLimit); - mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + mMaxSatiatedConsumptionLimit = getConstantAsCake( KEY_JS_MAX_CONSUMPTION_LIMIT, DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES, mInitialSatiatedConsumptionLimit); mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MAX_START_CTP, DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE, DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MAX_RUNNING, new Action(ACTION_JOB_MAX_RUNNING, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MAX_RUNNING_CTP, DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE, DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_HIGH_START, new Action(ACTION_JOB_HIGH_START, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_HIGH_START_CTP, DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE, DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_HIGH_RUNNING, new Action(ACTION_JOB_HIGH_RUNNING, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP, DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE, DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_DEFAULT_START, new Action(ACTION_JOB_DEFAULT_START, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_DEFAULT_START_CTP, DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE, DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_DEFAULT_RUNNING, new Action(ACTION_JOB_DEFAULT_RUNNING, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP, DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE, DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_LOW_START, new Action(ACTION_JOB_LOW_START, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_LOW_START_CTP, DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE, DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_LOW_RUNNING, new Action(ACTION_JOB_LOW_RUNNING, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_LOW_RUNNING_CTP, DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE, DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MIN_START, new Action(ACTION_JOB_MIN_START, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MIN_START_CTP, DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE, DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_MIN_RUNNING, new Action(ACTION_JOB_MIN_RUNNING, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MIN_RUNNING_CTP, DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE, DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES))); mActions.put(ACTION_JOB_TIMEOUT, new Action(ACTION_JOB_TIMEOUT, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP, DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE, DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES))); mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_TOP_ACTIVITY_INSTANT, DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_TOP_ACTIVITY_ONGOING, DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_TOP_ACTIVITY_MAX, DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT, DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING, DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_NOTIFICATION_SEEN_MAX, DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES))); mRewards.put(REWARD_NOTIFICATION_INTERACTION, new Reward(REWARD_NOTIFICATION_INTERACTION, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT, DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING, DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX, DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT, DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING, DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_WIDGET_INTERACTION_MAX, DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES))); mRewards.put(REWARD_OTHER_USER_INTERACTION, new Reward(REWARD_OTHER_USER_INTERACTION, - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT, DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING, DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( 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, + getConstantAsCake( KEY_JS_REWARD_APP_INSTALL_INSTANT, DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_APP_INSTALL_ONGOING, DEFAULT_JS_REWARD_APP_INSTALL_ONGOING_CAKES), - getConstantAsCake(mParser, properties, + getConstantAsCake( KEY_JS_REWARD_APP_INSTALL_MAX, DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES))); } diff --git a/core/api/current.txt b/core/api/current.txt index 309135411137..5b339fa9494d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9854,7 +9854,7 @@ package android.content { method @NonNull public android.content.AttributionSource build(); method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); - method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); + method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); method @NonNull public android.content.AttributionSource.Builder setPid(int); @@ -10601,6 +10601,7 @@ package android.content { field public static final String RESTRICTIONS_SERVICE = "restrictions"; field public static final String ROLE_SERVICE = "role"; field public static final String SEARCH_SERVICE = "search"; + field @FlaggedApi("android.os.security_state_service") public static final String SECURITY_STATE_SERVICE = "security_state"; field public static final String SENSOR_SERVICE = "sensor"; field public static final String SHORTCUT_SERVICE = "shortcut"; field public static final String STATUS_BAR_SERVICE = "statusbar"; @@ -10613,6 +10614,7 @@ package android.content { field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification"; field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; + field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String TV_AD_SERVICE = "tv_ad"; field public static final String TV_INPUT_SERVICE = "tv_input"; field public static final String TV_INTERACTIVE_APP_SERVICE = "tv_interactive_app"; field public static final String UI_MODE_SERVICE = "uimode"; @@ -11101,6 +11103,7 @@ package android.content { field public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED"; field @Deprecated public static final String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED"; field @Deprecated public static final String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED"; + field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; field @Deprecated public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE"; field public static final String ACTION_USER_BACKGROUND = "android.intent.action.USER_BACKGROUND"; field public static final String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND"; @@ -12366,6 +12369,7 @@ package android.content.pm { public class PackageInfo implements android.os.Parcelable { ctor public PackageInfo(); method public int describeContents(); + method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis(); method public long getLongVersionCode(); method public void setLongVersionCode(long); method public void writeToParcel(android.os.Parcel, int); @@ -12422,6 +12426,9 @@ package android.content.pm { method @NonNull public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException; method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback); method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler); + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender); method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender); method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, int, @NonNull android.content.IntentSender); @@ -12443,6 +12450,10 @@ package android.content.pm { field public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS"; field public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; field public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; field public static final int PACKAGE_SOURCE_DOWNLOADED_FILE = 4; // 0x4 field public static final int PACKAGE_SOURCE_LOCAL_FILE = 3; // 0x3 field public static final int PACKAGE_SOURCE_OTHER = 1; // 0x1 @@ -12458,6 +12469,13 @@ package android.content.pm { field public static final int STATUS_FAILURE_TIMEOUT = 8; // 0x8 field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff field public static final int STATUS_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0 } public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable { @@ -12621,6 +12639,7 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.ENFORCE_UPDATE_OWNERSHIP) public void setRequestUpdateOwnership(boolean); method public void setRequireUserAction(int); method public void setSize(long); + method @FlaggedApi("android.content.pm.archiving") public void setUnarchiveId(int); method public void setWhitelistedRestrictedPermissions(@Nullable java.util.Set<java.lang.String>); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionParams> CREATOR; @@ -12650,6 +12669,7 @@ package android.content.pm { method public void writeToParcel(android.os.Parcel, int); field public int banner; field public int icon; + field @FlaggedApi("android.content.pm.archiving") public boolean isArchived; field public int labelRes; field public int logo; field public android.os.Bundle metaData; @@ -12772,6 +12792,7 @@ package android.content.pm { method public boolean hasSigningCertificate(int, @NonNull byte[], int); method public abstract boolean hasSystemFeature(@NonNull String); method public abstract boolean hasSystemFeature(@NonNull String, int); + method @FlaggedApi("android.content.pm.archiving") public boolean isAppArchivable(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean isAutoRevokeWhitelisted(@NonNull String); method public boolean isAutoRevokeWhitelisted(); method public boolean isDefaultApplicationIcon(@NonNull android.graphics.drawable.Drawable); @@ -13013,6 +13034,7 @@ package android.content.pm { field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1 field public static final int MATCH_ALL = 131072; // 0x20000 field public static final int MATCH_APEX = 1073741824; // 0x40000000 + field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000 field public static final int MATCH_DIRECT_BOOT_AUTO = 268435456; // 0x10000000 field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000 @@ -22034,6 +22056,19 @@ package android.media { method public void onJetUserIdUpdate(android.media.JetPlayer, int, int); } + @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator { + method @FlaggedApi("android.media.audio.loudness_configurator_api") public void addMediaCodec(@NonNull android.media.MediaCodec); + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(); + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener); + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec); + method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec); + method @FlaggedApi("android.media.audio.loudness_configurator_api") public void setAudioTrack(@Nullable android.media.AudioTrack); + } + + @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener { + method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public default android.os.Bundle onLoudnessCodecUpdate(@NonNull android.media.MediaCodec, @NonNull android.os.Bundle); + } + public class MediaActionSound { ctor public MediaActionSound(); method public void load(int); @@ -27302,6 +27337,13 @@ package android.media.tv { } +package android.media.tv.ad { + + @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager { + } + +} + package android.media.tv.interactive { public final class AppLinkInfo implements android.os.Parcelable { @@ -32840,7 +32882,7 @@ package android.os { method public static android.os.Message obtain(android.os.Handler, int, Object); method public static android.os.Message obtain(android.os.Handler, int, int, int); method public static android.os.Message obtain(android.os.Handler, int, int, int, Object); - method public android.os.Bundle peekData(); + method @Nullable public android.os.Bundle peekData(); method public void recycle(); method public void sendToTarget(); method public void setAsynchronous(boolean); @@ -33379,6 +33421,13 @@ package android.os { field @NonNull public static final android.os.Parcelable.Creator<android.os.ResultReceiver> CREATOR; } + @FlaggedApi("android.os.security_state_service") public class SecurityStateManager { + method @FlaggedApi("android.os.security_state_service") @NonNull public android.os.Bundle getGlobalSecurityState(); + field public static final String KEY_KERNEL_VERSION = "kernel_version"; + field public static final String KEY_SYSTEM_SPL = "system_spl"; + field public static final String KEY_VENDOR_SPL = "vendor_spl"; + } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { method public void close(); method @NonNull public static android.os.SharedMemory create(@Nullable String, int) throws android.system.ErrnoException; @@ -36773,7 +36822,7 @@ package android.provider { field public static final String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS"; field public static final String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; field public static final String ACTION_CONDITION_PROVIDER_SETTINGS = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS"; - field public static final String ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER"; + field @FlaggedApi("android.credentials.flags.new_settings_intents") public static final String ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER"; field public static final String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS"; field public static final String ACTION_DATA_USAGE_SETTINGS = "android.settings.DATA_USAGE_SETTINGS"; field public static final String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ce5752fdbd8b..0497c6087ccd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -258,6 +258,7 @@ package android { field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"; field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION"; field public static final String POWER_SAVER = "android.permission.POWER_SAVER"; + field @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis") public static final String PREPARE_FACTORY_RESET = "android.permission.PREPARE_FACTORY_RESET"; field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"; field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT"; @@ -3613,7 +3614,6 @@ package android.content { field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; - field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; @@ -3861,16 +3861,9 @@ package android.content.pm { field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800 } - public class PackageInfo implements android.os.Parcelable { - method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis(); - } - public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @FlaggedApi("android.content.pm.read_install_info") @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; @@ -3881,23 +3874,12 @@ package android.content.pm { field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS"; field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH"; - field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; - field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; - field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; - field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; field public static final int LOCATION_DATA_APP = 0; // 0x0 field public static final int LOCATION_MEDIA_DATA = 2; // 0x2 field public static final int LOCATION_MEDIA_OBB = 1; // 0x1 field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0 field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1 field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64 - field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0 } public static class PackageInstaller.InstallInfo { @@ -3947,14 +3929,12 @@ package android.content.pm { method public void setRequestDowngrade(boolean); method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged(); - method @FlaggedApi("android.content.pm.archiving") public void setUnarchiveId(int); } public class PackageItemInfo { method public static void forceSafeLabels(); method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager); method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int); - field @FlaggedApi("android.content.pm.archiving") public boolean isArchived; } public abstract class PackageManager { @@ -3986,7 +3966,6 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") public boolean isAppArchivable(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); @@ -4104,7 +4083,6 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 @@ -12213,6 +12191,7 @@ package android.service.notification { method public boolean hasExpanded(); method public boolean hasInteracted(); method public boolean hasSeen(); + method @FlaggedApi("android.app.lifetime_extension_refactor") public boolean hasSmartReplied(); method public boolean hasSnoozed(); method public boolean hasViewedSettings(); method public void setDirectReplied(); @@ -12220,6 +12199,7 @@ package android.service.notification { method public void setDismissalSurface(int); method public void setExpanded(); method public void setSeen(); + method @FlaggedApi("android.app.lifetime_extension_refactor") public void setSmartReplied(); method public void setSnoozed(); method public void setViewedSettings(); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1f7390c094f4..f4c8429619dd 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -780,6 +780,25 @@ package android.app.job { } +package android.app.pinner { + + @FlaggedApi("android.app.pinner_service_client_api") public final class PinnedFileStat implements android.os.Parcelable { + ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnedFileStat(@NonNull String, long, @NonNull String); + method @FlaggedApi("android.app.pinner_service_client_api") public int describeContents(); + method @FlaggedApi("android.app.pinner_service_client_api") public long getBytesPinned(); + method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public String getFilename(); + method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public String getGroupName(); + method @FlaggedApi("android.app.pinner_service_client_api") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.app.pinner_service_client_api") @NonNull public static final android.os.Parcelable.Creator<android.app.pinner.PinnedFileStat> CREATOR; + } + + @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient { + ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient(); + method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public java.util.List<android.app.pinner.PinnedFileStat> getPinnerStats(); + } + +} + package android.app.prediction { public final class AppPredictor { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4f8e8dd813a1..014ddd41f8d4 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3484,9 +3484,20 @@ class ContextImpl extends Context { // only do this if the user already has more than one preferred locale if (android.content.res.Flags.defaultLocale() && r.getConfiguration().getLocales().size() > 1) { - LocaleConfig lc = getUserId() < 0 - ? LocaleConfig.fromContextIgnoringOverride(this) - : new LocaleConfig(this); + LocaleConfig lc; + if (getUserId() < 0) { + lc = LocaleConfig.fromContextIgnoringOverride(this); + } else { + // This is needed because the app might have locale config overrides that need to + // be read from disk in order for resources to correctly choose which values to + // load. + StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads(); + try { + lc = new LocaleConfig(this); + } finally { + StrictMode.setThreadPolicy(policy); + } + } mResourcesManager.setLocaleConfig(lc); } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 337e3f1195be..8c5773a05764 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -745,6 +745,16 @@ public class Notification implements Parcelable @TestApi public static final int FLAG_USER_INITIATED_JOB = 0x00008000; + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if this notification has been lifetime extended due to a direct reply. + * + * This flag is for internal use only; applications cannot set this flag directly. + * @hide + */ + @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public static final int FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY = 0x00010000; + private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index d8448dcb4f9c..772b0b46d3b4 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -85,6 +85,9 @@ per-file IEphemeralResolver.aidl = file:/services/core/java/com/android/server/p per-file IInstantAppResolver.aidl = file:/services/core/java/com/android/server/pm/OWNERS per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS +# Pinner +per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS + # ResourcesManager per-file ResourcesManager.java = file:RESOURCES_OWNERS diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 6009c29ae53c..24a51573b48a 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -159,7 +159,8 @@ public class ResourcesManager { * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the * instance is alive and reachable. */ - private class ApkAssetsSupplier { + @VisibleForTesting + protected class ApkAssetsSupplier { final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>(); /** @@ -544,7 +545,10 @@ public class ResourcesManager { * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using * {@link #loadApkAssets(ApkKey)}. */ - private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key, + + @VisibleForTesting + @UnsupportedAppUsage + protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { final AssetManager.Builder builder = new AssetManager.Builder(); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 79a5879b5cc0..9cf732abb86a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -137,6 +137,8 @@ import android.media.projection.MediaProjectionManager; import android.media.soundtrigger.SoundTriggerManager; import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; +import android.media.tv.ad.ITvAdManager; +import android.media.tv.ad.TvAdManager; import android.media.tv.interactive.ITvInteractiveAppManager; import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.tunerresourcemanager.ITunerResourceManager; @@ -174,6 +176,7 @@ import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IPowerStatsService; import android.os.IRecoverySystem; +import android.os.ISecurityStateManager; import android.os.ISystemUpdateManager; import android.os.IThermalService; import android.os.IUserManager; @@ -182,6 +185,7 @@ import android.os.PerformanceHintManager; import android.os.PermissionEnforcer; import android.os.PowerManager; import android.os.RecoverySystem; +import android.os.SecurityStateManager; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.StatsFrameworkInitializer; @@ -628,6 +632,17 @@ public final class SystemServiceRegistry { ctx.mMainThread.getHandler()); }}); + registerService(Context.SECURITY_STATE_SERVICE, SecurityStateManager.class, + new CachedServiceFetcher<SecurityStateManager>() { + @Override + public SecurityStateManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.SECURITY_STATE_SERVICE); + ISecurityStateManager service = ISecurityStateManager.Stub.asInterface(b); + return new SecurityStateManager(service); + }}); + registerService(Context.SENSOR_SERVICE, SensorManager.class, new CachedServiceFetcher<SensorManager>() { @Override @@ -960,6 +975,18 @@ public final class SystemServiceRegistry { return new TvInteractiveAppManager(service, ctx.getUserId()); }}); + registerService(Context.TV_AD_SERVICE, TvAdManager.class, + new CachedServiceFetcher<TvAdManager>() { + @Override + public TvAdManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = + ServiceManager.getServiceOrThrow(Context.TV_AD_SERVICE); + ITvAdManager service = + ITvAdManager.Stub.asInterface(iBinder); + return new TvAdManager(service, ctx.getUserId()); + }}); + registerService(Context.TV_INPUT_SERVICE, TvInputManager.class, new CachedServiceFetcher<TvInputManager>() { @Override diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 6a03c17159d3..ce1d43d10c34 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -180,6 +180,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @Override public void injectInputEventToInputFilter(InputEvent event) throws RemoteException { + synchronized (mLock) { + throwIfCalledByNotTrustedUidLocked(); + throwIfShutdownLocked(); + throwIfNotConnectedLocked(); + } mAccessibilityManager.injectInputEventToInputFilter(event); } diff --git a/core/java/android/app/pinner-client.aconfig b/core/java/android/app/pinner-client.aconfig new file mode 100644 index 000000000000..b60ad9ee1f8d --- /dev/null +++ b/core/java/android/app/pinner-client.aconfig @@ -0,0 +1,8 @@ +package: "android.app" + +flag { + namespace: "system_performance" + name: "pinner_service_client_api" + description: "Control exposing PinnerService APIs." + bug: "307594624" +}
\ No newline at end of file diff --git a/core/java/android/app/pinner/IPinnerService.aidl b/core/java/android/app/pinner/IPinnerService.aidl new file mode 100644 index 000000000000..e5d0a05dd259 --- /dev/null +++ b/core/java/android/app/pinner/IPinnerService.aidl @@ -0,0 +1,12 @@ +package android.app.pinner; + +import android.app.pinner.PinnedFileStat; + +/** + * Interface for processes to communicate with system's PinnerService. + * @hide + */ +interface IPinnerService { + @EnforcePermission("DUMP") + List<PinnedFileStat> getPinnerStats(); +}
\ No newline at end of file diff --git a/core/java/android/app/pinner/OWNERS b/core/java/android/app/pinner/OWNERS new file mode 100644 index 000000000000..3e3fa66ca916 --- /dev/null +++ b/core/java/android/app/pinner/OWNERS @@ -0,0 +1,10 @@ +carmenjackson@google.com +dualli@google.com +edgararriaga@google.com +kevinjeon@google.com +philipcuadra@google.com +shombert@google.com +timmurray@google.com +wessam@google.com +jdduke@google.com +shayba@google.com
\ No newline at end of file diff --git a/core/java/android/app/pinner/PinnedFileStat.aidl b/core/java/android/app/pinner/PinnedFileStat.aidl new file mode 100644 index 000000000000..44217cf57d4d --- /dev/null +++ b/core/java/android/app/pinner/PinnedFileStat.aidl @@ -0,0 +1,3 @@ +package android.app.pinner; + +parcelable PinnedFileStat;
\ No newline at end of file diff --git a/core/java/android/app/pinner/PinnedFileStat.java b/core/java/android/app/pinner/PinnedFileStat.java new file mode 100644 index 000000000000..2e36330fc6df --- /dev/null +++ b/core/java/android/app/pinner/PinnedFileStat.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 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.app.pinner; + +import static android.app.Flags.FLAG_PINNER_SERVICE_CLIENT_API; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +@TestApi +@FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) +public final class PinnedFileStat implements Parcelable { + private String filename; + private long bytesPinned; + private String groupName; + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public long getBytesPinned() { + return bytesPinned; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public @NonNull String getFilename() { + return filename; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public @NonNull String getGroupName() { + return groupName; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public PinnedFileStat(@NonNull String filename, long bytesPinned, @NonNull String groupName) { + this.filename = filename; + this.bytesPinned = bytesPinned; + this.groupName = groupName; + } + + private PinnedFileStat(Parcel source) { + readFromParcel(source); + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(filename); + dest.writeLong(bytesPinned); + dest.writeString8(groupName); + } + + private void readFromParcel(@NonNull Parcel source) { + filename = source.readString8(); + bytesPinned = source.readLong(); + groupName = source.readString8(); + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public int describeContents() { + return 0; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public static final @NonNull Creator<PinnedFileStat> CREATOR = new Creator<>() { + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public PinnedFileStat createFromParcel(Parcel source) { + return new PinnedFileStat(source); + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public PinnedFileStat[] newArray(int size) { + return new PinnedFileStat[size]; + } + }; +} diff --git a/core/java/android/app/pinner/PinnerServiceClient.java b/core/java/android/app/pinner/PinnerServiceClient.java new file mode 100644 index 000000000000..8b7c6ccb51f2 --- /dev/null +++ b/core/java/android/app/pinner/PinnerServiceClient.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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.app.pinner; + +import static android.app.Flags.FLAG_PINNER_SERVICE_CLIENT_API; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.app.pinner.IPinnerService; +import android.app.pinner.PinnedFileStat; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Expose PinnerService as an interface to apps. + * @hide + */ +@TestApi +@FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) +public class PinnerServiceClient { + private static String TAG = "PinnerServiceClient"; + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public PinnerServiceClient() {} + + /** + * Obtain the pinned file stats used for testing infrastructure. + * @return List of pinned files or an empty list if failed to retrieve them. + * @throws RuntimeException on failure to retrieve stats. + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public @NonNull List<PinnedFileStat> getPinnerStats() { + IBinder binder = ServiceManager.getService("pinner"); + if (binder == null) { + Slog.w(TAG, + "Failed to retrieve PinnerService. A common failure reason is due to a lack of selinux permissions."); + return new ArrayList<>(); + } + IPinnerService pinnerService = IPinnerService.Stub.asInterface(binder); + if (pinnerService == null) { + Slog.w(TAG, "Failed to cast PinnerService."); + return new ArrayList<>(); + } + List<PinnedFileStat> stats; + try { + stats = pinnerService.getPinnerStats(); + } catch (RemoteException e) { + throw new RuntimeException("Failed to retrieve stats from PinnerService"); + } + return stats; + } +} diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 0ae00cd7d133..9eb73b32a2a7 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -837,6 +837,7 @@ public final class UsageEvents implements Parcelable { if (mEventCount != mEventsToWrite.size()) { Log.w(TAG, "Partial usage event list received: " + mEventCount + " != " + mEventsToWrite.size()); + mEventCount = mEventsToWrite.size(); } } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index c6012bbc7292..6e451479c5a4 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -52,6 +52,7 @@ import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.FunctionalUtils; import java.util.ArrayList; import java.util.Collections; @@ -557,11 +558,45 @@ public class AppWidgetManager { } }).toArray(ComponentName[]::new)); } catch (Exception e) { - Log.e(TAG, "Nofity service of inheritance info", e); + Log.e(TAG, "Notify service of inheritance info", e); } }); } + private void tryAdapterConversion( + FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, + RemoteViews original, String failureMsg) { + final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled() + && (mHasPostedLegacyLists = mHasPostedLegacyLists + || (original != null && original.hasLegacyLists())); + + if (isConvertingAdapter) { + final RemoteViews viewsCopy = new RemoteViews(original); + Runnable updateWidgetWithTask = () -> { + try { + viewsCopy.collectAllIntents().get(); + action.acceptOrThrow(viewsCopy); + } catch (Exception e) { + Log.e(TAG, failureMsg, e); + } + }; + + if (Looper.getMainLooper() == Looper.myLooper()) { + createUpdateExecutorIfNull().execute(updateWidgetWithTask); + return; + } + + updateWidgetWithTask.run(); + return; + } + + try { + action.acceptOrThrow(original); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Set the RemoteViews to use for the specified appWidgetIds. * <p> @@ -586,32 +621,8 @@ public class AppWidgetManager { return; } - final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled() - && (mHasPostedLegacyLists = mHasPostedLegacyLists - || (views != null && views.hasLegacyLists())); - - if (isConvertingAdapter) { - views.collectAllIntents(); - - if (Looper.getMainLooper() == Looper.myLooper()) { - RemoteViews viewsCopy = new RemoteViews(views); - createUpdateExecutorIfNull().execute(() -> { - try { - mService.updateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy); - } catch (RemoteException e) { - Log.e(TAG, "Error updating app widget views in background", e); - } - }); - - return; - } - } - - try { - mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds, + view), views, "Error updating app widget views in background"); } /** @@ -716,32 +727,9 @@ public class AppWidgetManager { return; } - final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled() - && (mHasPostedLegacyLists = mHasPostedLegacyLists - || (views != null && views.hasLegacyLists())); - - if (isConvertingAdapter) { - views.collectAllIntents(); - - if (Looper.getMainLooper() == Looper.myLooper()) { - RemoteViews viewsCopy = new RemoteViews(views); - createUpdateExecutorIfNull().execute(() -> { - try { - mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy); - } catch (RemoteException e) { - Log.e(TAG, "Error partially updating app widget views in background", e); - } - }); - - return; - } - } - - try { - mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName, + appWidgetIds, view), views, + "Error partially updating app widget views in background"); } /** @@ -793,33 +781,8 @@ public class AppWidgetManager { return; } - final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled() - && (mHasPostedLegacyLists = mHasPostedLegacyLists - || (views != null && views.hasLegacyLists())); - - if (isConvertingAdapter) { - views.collectAllIntents(); - - if (Looper.getMainLooper() == Looper.myLooper()) { - RemoteViews viewsCopy = new RemoteViews(views); - createUpdateExecutorIfNull().execute(() -> { - try { - mService.updateAppWidgetProvider(provider, viewsCopy); - } catch (RemoteException e) { - Log.e(TAG, "Error updating app widget view using provider in background", - e); - } - }); - - return; - } - } - - try { - mService.updateAppWidgetProvider(provider, views); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views, + "Error updating app widget view using provider in background"); } /** diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 102cbf3a7e31..3520c0b4d724 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -246,4 +246,10 @@ interface IVirtualDevice { */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") void unregisterVirtualCamera(in VirtualCameraConfig camera); + + /** + * Returns the id of the virtual camera with given config. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + int getVirtualCameraId(in VirtualCameraConfig camera); } diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index da8277c24f6c..9492a62a7f67 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -19,6 +19,7 @@ package android.companion.virtual; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.virtual.audio.VirtualAudioDevice; @@ -340,12 +341,17 @@ public class VirtualDeviceInternal { return mVirtualAudioDevice; } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) { - return new VirtualCamera(mVirtualDevice, config); + try { + mVirtualDevice.registerVirtualCamera(config); + return new VirtualCamera(mVirtualDevice, config); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } - @NonNull void setShowPointerIcon(boolean showPointerIcon) { try { mVirtualDevice.setShowPointerIcon(showPointerIcon); diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java index beee86fcfac2..52afa4eda2f8 100644 --- a/core/java/android/companion/virtual/camera/VirtualCamera.java +++ b/core/java/android/companion/virtual/camera/VirtualCamera.java @@ -66,15 +66,8 @@ public final class VirtualCamera implements Closeable { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public VirtualCamera( @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) { - mVirtualDevice = virtualDevice; + mVirtualDevice = Objects.requireNonNull(virtualDevice); mConfig = Objects.requireNonNull(config); - Objects.requireNonNull(virtualDevice); - // TODO(b/310857519): Avoid registration inside constructor. - try { - mVirtualDevice.registerVirtualCamera(config); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } } /** Returns the configuration of this virtual camera instance. */ @@ -83,6 +76,20 @@ public final class VirtualCamera implements Closeable { return mConfig; } + /** + * Returns the id of this virtual camera instance. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public String getId() { + try { + return Integer.toString(mVirtualDevice.getVirtualCameraId(mConfig)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 10da8b1c8203..f0477d47f723 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -32,6 +32,13 @@ flag { } flag { + name: "consistent_display_flags" + namespace: "virtual_devices" + description: "Make virtual display flags consistent with display manager" + bug: "300905478" +} + +flag { name: "vdm_custom_home" namespace: "virtual_devices" description: "Enable custom home API" diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 4b2cee698df2..697c25c2a1ec 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -730,10 +730,7 @@ public final class AttributionSource implements Parcelable { /** * The next app to receive the permission protected data. - * - * @deprecated Use {@link #setNextAttributionSource} instead. */ - @Deprecated public @NonNull Builder setNext(@Nullable AttributionSource value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 1c6c7b5baa58..b75c64dcc3c1 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -72,6 +72,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Flags; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; @@ -4214,6 +4215,7 @@ public abstract class Context { DEVICE_LOCK_SERVICE, VIRTUALIZATION_SERVICE, GRAMMATICAL_INFLECTION_SERVICE, + SECURITY_STATE_SERVICE, }) @Retention(RetentionPolicy.SOURCE) @@ -5818,6 +5820,17 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.media.tv.ad.TvAdManager} for interacting with TV client-side advertisement + * services on the device. + * + * @see #getSystemService(String) + * @see android.media.tv.ad.TvAdManager + */ + @FlaggedApi(android.media.tv.flags.Flags.FLAG_ENABLE_AD_SERVICE_FW) + public static final String TV_AD_SERVICE = "tv_ad"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link android.media.tv.TunerResourceManager} for interacting with TV * tuner resources on the device. * @@ -6478,6 +6491,16 @@ public abstract class Context { public static final String SHARED_CONNECTIVITY_SERVICE = "shared_connectivity"; /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.os.SecurityStateManager} for accessing the security state manager service. + * + * @see #getSystemService(String) + * @see android.os.SecurityStateManager + */ + @FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) + public static final String SECURITY_STATE_SERVICE = "security_state"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index c7a86fbe0171..38bcfa220af4 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5353,11 +5353,11 @@ public class Intent implements Parcelable, Cloneable { * Broadcast Action: Sent to the responsible installer of an archived package when unarchival * is requested. * - * @see android.content.pm.PackageInstaller#requestUnarchive(String) - * @hide + * @see android.content.pm.PackageInstaller#requestUnarchive */ - @SystemApi @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) + @BroadcastBehavior(explicitOnly = true) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; // --------------------------------------------------------------------- diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 1cfdb8b37fcd..5736a6d8cb4a 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -20,7 +20,6 @@ import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -522,9 +521,7 @@ public class PackageInfo implements Parcelable { /** * Returns the time at which the app was archived for the user. Units are as * per {@link System#currentTimeMillis()}. - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public @CurrentTimeMillisLong long getArchiveTimeMillis() { return mArchiveTimeMillis; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 6df1f600c3ef..d35c3922e9b7 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -354,10 +354,7 @@ public class PackageInstaller { /** * Extra field for the package name of a package that is requested to be unarchived. Sent as * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; @@ -366,22 +363,16 @@ public class PackageInstaller { * Extra field for the unarchive ID. Sent as * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. * - * @see Session#setUnarchiveId(int) - * - * @hide + * @see SessionParams#setUnarchiveId */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; /** * If true, the requestor of the unarchival has specified that the app should be unarchived - * for {@link android.os.UserHandle#ALL}. - * - * @hide + * for all users. */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; @@ -398,9 +389,7 @@ public class PackageInstaller { * failure dialog. * * @see #requestUnarchive - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; @@ -675,10 +664,7 @@ public class PackageInstaller { * * <p> Note that this does not mean that the unarchival has completed. This status should be * sent before any longer asynchronous action (e.g. app download) is started. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_OK = 0; @@ -687,10 +673,7 @@ public class PackageInstaller { * * <p> An example use case for this could be that the user needs to login to allow the * download for a paid app. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; @@ -700,19 +683,13 @@ public class PackageInstaller { * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing * dialog. If no action is provided, then a generic intent * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; /** * The device is not connected to the internet - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; @@ -720,10 +697,7 @@ public class PackageInstaller { * The installer responsible for the unarchival is disabled. * * <p> Should only be used by the system. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; @@ -731,19 +705,13 @@ public class PackageInstaller { * The installer responsible for the unarchival has been uninstalled * * <p> Should only be used by the system. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; /** * Generic error: The app cannot be unarchived. - * - * @hide */ - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_GENERIC_ERROR = 100; @@ -2364,12 +2332,10 @@ public class PackageInstaller { * @param statusReceiver Callback used to notify when the operation is completed. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * available to the caller or isn't archived. - * @hide */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws PackageManager.NameNotFoundException { @@ -2395,19 +2361,16 @@ public class PackageInstaller { * * @param statusReceiver Callback used to notify whether the installer has accepted the * unarchival request or an error has occurred. The status update will be - * sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be + * sent though {@link #EXTRA_UNARCHIVE_STATUS}. Only one status will be * sent. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * visible to the caller or if the package has no * installer on the device anymore to unarchive it. * @throws IOException If parameters were unsatisfiable, such as lack of disk space. - * - * @hide */ @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.REQUEST_INSTALL_PACKAGES}) - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws IOException, PackageManager.NameNotFoundException { @@ -2435,12 +2398,10 @@ public class PackageInstaller { * @param userActionIntent Optional intent to start a follow up action required to * facilitate the unarchival flow (e.g. user needs to log in). * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists - * @hide */ @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.REQUEST_INSTALL_PACKAGES}) - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status, long requiredStorageBytes, @Nullable PendingIntent userActionIntent) @@ -3454,11 +3415,8 @@ public class PackageInstaller { * <p> The ID should be retrieved from the unarchive intent and passed into the * session that's being created to unarchive the app in question. Used to link the unarchive * intent and the install session to disambiguate. - * - * @hide */ @FlaggedApi(Flags.FLAG_ARCHIVING) - @SystemApi public void setUnarchiveId(int unarchiveId) { this.unarchiveId = unarchiveId; } diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index c7091ad99199..70e6f9864eb6 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -177,12 +177,10 @@ public class PackageItemInfo { /** * Whether the package is currently in an archived state. * - * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored - * on the device, but do keep the data directory. - * @hide + * <p>Packages can be archived through {@link PackageInstaller#requestArchive} and do not have + * any APKs stored on the device, but do keep the data directory. + * */ - // TODO(b/278553670) Unhide and update @links before launch. - @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6775f9b8d84d..a22fe3f1452b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1263,18 +1263,14 @@ public abstract class PackageManager { /** * Flag parameter to also retrieve some information about archived packages. - * Packages can be archived through - * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored - * on the device, but do keep the data directory. + * Packages can be archived through {@link PackageInstaller#requestArchive} and do not have any + * APKs stored on the device, but do keep the data directory. * <p> Note: Archived apps are a subset of apps returned by {@link #MATCH_UNINSTALLED_PACKAGES}. * <p> Note: this flag may cause less information about currently installed * applications to be returned. * <p> Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES * permission to see uninstalled packages. - * @hide */ - // TODO(b/278553670) Unhide and update @links before launch. - @SystemApi @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32; @@ -8969,10 +8965,7 @@ public abstract class PackageManager { * * @throws NameNotFoundException if the given package name is not available to the caller. * @see PackageInstaller#requestArchive(String, IntentSender) - * - * @hide */ - @SystemApi @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public boolean isAppArchivable(@NonNull String packageName) throws NameNotFoundException { throw new UnsupportedOperationException("isAppArchivable not implemented"); diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index a565f6825e7a..d21b81854584 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -94,3 +94,10 @@ flag { description: "Feature flag to read install related information from an APK." bug: "275658500" } + +flag { + name: "use_pia_v2" + namespace: "package_manager_service" + description: "Feature flag to enable the refactored Package Installer app with updated UI." + bug: "182205982" +} diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index 1b8eb0748737..40592a151fa7 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -15,3 +15,12 @@ flag { description: "Feature flag for passing in an AssetFileDescriptor to create an frro" bug: "304478666" } + +flag { + name: "manifest_flagging" + namespace: "resource_manager" + description: "Feature flag for flagging manifest entries" + bug: "297373084" + # This flag is read in PackageParser at boot time, and in aapt2 which is a build tool. + is_fixed_read_only: true +} diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index d786d9a20189..18c95bfbb297 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -66,6 +66,13 @@ public final class SensorPrivacyManager { + ".extra.sensor"; /** + * An extra containing the notification id that triggered the intent + * @hide + */ + public static final String EXTRA_NOTIFICATION_ID = SensorPrivacyManager.class.getName() + + ".extra.notification_id"; + + /** * An extra indicating if all sensors are affected * @hide */ diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index dfc27caa362c..507e8140ff61 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -16,6 +16,7 @@ package android.hardware.camera2; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -24,6 +25,8 @@ import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.util.Log; +import com.android.internal.camera.flags.Flags; + import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java index d4ce0ebbc528..5cbb0bbadc4c 100644 --- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java @@ -16,8 +16,6 @@ package android.hardware.camera2.params; -import static com.android.internal.R.string.hardware; - import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index ed3100251040..eabe13b0c54f 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -123,6 +123,12 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000; + private static final int[] UID_USAGE_TIME_PROCESS_STATES = { + BatteryConsumer.PROCESS_STATE_FOREGROUND, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE + }; + private final int mDischargePercentage; private final double mBatteryCapacityMah; private final long mStatsStartTimestampMs; @@ -516,6 +522,22 @@ public final class BatteryUsageStats implements Parcelable, Closeable { proto.write( BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS, bgMs); + for (int processState : UID_USAGE_TIME_PROCESS_STATES) { + final long timeInStateMillis = consumer.getTimeInProcessStateMs(processState); + if (timeInStateMillis <= 0) { + continue; + } + final long timeInStateToken = proto.start( + BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_STATE); + proto.write( + BatteryUsageStatsAtomsProto.UidBatteryConsumer.TimeInState.PROCESS_STATE, + processState); + proto.write( + BatteryUsageStatsAtomsProto.UidBatteryConsumer.TimeInState + .TIME_IN_STATE_MILLIS, + timeInStateMillis); + proto.end(timeInStateToken); + } proto.end(token); if (proto.getRawSize() >= maxRawSize) { diff --git a/core/java/android/os/ISecurityStateManager.aidl b/core/java/android/os/ISecurityStateManager.aidl new file mode 100644 index 000000000000..8ae624d0371d --- /dev/null +++ b/core/java/android/os/ISecurityStateManager.aidl @@ -0,0 +1,26 @@ +/* //device/java/android/android/os/ISecurityStateManager.aidl +** +** Copyright 2023, 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.os; + +import android.os.Bundle; +import android.os.PersistableBundle; + +/** @hide */ +interface ISecurityStateManager { + Bundle getGlobalSecurityState(); +} diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index da647e2b78cb..161951ead77f 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -437,6 +438,7 @@ public final class Message implements Parcelable { * @see #getData() * @see #setData(Bundle) */ + @Nullable public Bundle peekData() { return data; } diff --git a/core/java/android/os/SecurityStateManager.java b/core/java/android/os/SecurityStateManager.java new file mode 100644 index 000000000000..4fa61e0ca782 --- /dev/null +++ b/core/java/android/os/SecurityStateManager.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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.os; + +import static java.util.Objects.requireNonNull; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemService; +import android.content.Context; + +/** + * SecurityStateManager provides the functionality to query the security status of the system and + * platform components. For example, this includes the system and vendor security patch level. + */ +@FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) +@SystemService(Context.SECURITY_STATE_SERVICE) +public class SecurityStateManager { + + /** + * The system SPL key returned as part of the {@code Bundle} from + * {@code getGlobalSecurityState}. + */ + public static final String KEY_SYSTEM_SPL = "system_spl"; + + /** + * The vendor SPL key returned as part of the {@code Bundle} from + * {@code getGlobalSecurityState}. + */ + public static final String KEY_VENDOR_SPL = "vendor_spl"; + + /** + * The kernel version key returned as part of the {@code Bundle} from + * {@code getGlobalSecurityState}. + */ + public static final String KEY_KERNEL_VERSION = "kernel_version"; + + private final ISecurityStateManager mService; + + /** + * @hide + */ + public SecurityStateManager(ISecurityStateManager service) { + mService = requireNonNull(service, "missing ISecurityStateManager"); + } + + /** + * Returns the current global security state. Each key-value pair is a mapping of a component + * of the global security state to its current version/SPL (security patch level). For example, + * the {@code KEY_SYSTEM_SPL} key will map to the SPL of the system as defined in + * {@link android.os.Build.VERSION}. The bundle will also include mappings from WebView packages + * and packages listed under config {@code config_securityStatePackages} to their respective + * versions as defined in {@link android.content.pm.PackageInfo#versionName}. + * + * @return A {@code Bundle} that contains the global security state information as + * string-to-string key-value pairs. + */ + @FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE) + @NonNull + public Bundle getGlobalSecurityState() { + try { + return mService.getGlobalSecurityState(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index a78f221fc962..c085f334457a 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -57,6 +57,13 @@ flag { } flag { + name: "security_state_service" + namespace: "dynamic_spl" + description: "Guards the Security State API." + bug: "302189431" +} + +flag { name: "battery_saver_supported_check_api" namespace: "backstage_power" description: "Guards a new API in PowerManager to check if battery saver is supported or not." diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 6853892348d9..78a12f75a508 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1738,23 +1738,6 @@ public class StorageManager { return RoSystemProperties.CRYPTO_FILE_ENCRYPTED; } - /** {@hide} - * @deprecated Use {@link #isFileEncrypted} instead, since emulated FBE is no longer supported. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Deprecated - public static boolean isFileEncryptedNativeOnly() { - return isFileEncrypted(); - } - - /** {@hide} - * @deprecated Use {@link #isFileEncrypted} instead, since emulated FBE is no longer supported. - */ - @Deprecated - public static boolean isFileEncryptedNativeOrEmulated() { - return isFileEncrypted(); - } - /** {@hide} */ public static boolean hasAdoptable() { switch (SystemProperties.get(PROP_ADOPTABLE)) { diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index dc86e3f57c40..5cbc18e22386 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -56,4 +56,11 @@ flag { namespace: "permissions" description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER" bug: "222650148" -}
\ No newline at end of file +} + +flag { + name: "factory_reset_prep_permission_apis" + namespace: "wallet_integration" + description: "enable Permission PREPARE_FACTORY_RESET." + bug: "302016478" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ff6ec29bb8ac..7f64400a1cce 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2542,6 +2542,7 @@ public final class Settings { * ComponentName)} and only use this action to start an activity if they return {@code false}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @FlaggedApi(android.credentials.flags.Flags.FLAG_NEW_SETTINGS_INTENTS) public static final String ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER"; @@ -14975,6 +14976,38 @@ public final class Settings { public static final String APP_OPS_CONSTANTS = "app_ops_constants"; /** + * Device Idle (Doze) specific settings. + * This is encoded as a key=value list, separated by commas. Ex: + * + * "inactive_to=60000,sensing_to=400000" + * + * The following keys are supported: + * + * <pre> + * inactive_to (long) + * sensing_to (long) + * motion_inactive_to (long) + * idle_after_inactive_to (long) + * idle_pending_to (long) + * max_idle_pending_to (long) + * idle_pending_factor (float) + * quick_doze_delay_to (long) + * idle_to (long) + * max_idle_to (long) + * idle_factor (float) + * min_time_to_alarm (long) + * max_temp_app_whitelist_duration (long) + * notification_whitelist_duration (long) + * </pre> + * + * <p> + * Type: string + * @hide + * @see com.android.server.DeviceIdleController.Constants + */ + public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants"; + + /** * Battery Saver specific settings * This is encoded as a key=value list, separated by commas. Ex: * diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS index 33a67aed6023..533d459e532b 100644 --- a/core/java/android/security/OWNERS +++ b/core/java/android/security/OWNERS @@ -8,4 +8,4 @@ per-file *NetworkSecurityPolicy.java = file:net/OWNERS per-file Confirmation*.java = file:/keystore/OWNERS per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS -per-file *.aconfig = victorhsieh@google.com +per-file *.aconfig = victorhsieh@google.com,eranm@google.com diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 2a4cbaf79a75..46ea158b8f90 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -15,7 +15,6 @@ */ package android.service.notification; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.Notification; import android.os.Bundle; @@ -26,6 +25,7 @@ import android.system.ErrnoException; import android.system.OsConstants; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.nio.ByteBuffer; @@ -75,10 +75,6 @@ public class NotificationRankingUpdate implements Parcelable { } // We only need read-only access to the shared memory region. buffer = mRankingMapFd.mapReadOnly(); - if (buffer == null) { - mRankingMap = null; - return; - } byte[] payload = new byte[buffer.remaining()]; buffer.get(payload); mapParcel.unmarshall(payload, 0, payload.length); @@ -98,7 +94,7 @@ public class NotificationRankingUpdate implements Parcelable { } finally { mapParcel.recycle(); if (buffer != null && mRankingMapFd != null) { - mRankingMapFd.unmap(buffer); + SharedMemory.unmap(buffer); mRankingMapFd.close(); } } @@ -210,6 +206,7 @@ public class NotificationRankingUpdate implements Parcelable { new NotificationListenerService.Ranking[0] ) ); + ByteBuffer buffer = null; try { // Parcels the ranking map and measures its size. @@ -217,13 +214,10 @@ public class NotificationRankingUpdate implements Parcelable { int mapSize = mapParcel.dataSize(); // Creates a new SharedMemory object with enough space to hold the ranking map. - SharedMemory mRankingMapFd = SharedMemory.create(mSharedMemoryName, mapSize); - if (mRankingMapFd == null) { - return; - } + mRankingMapFd = SharedMemory.create(mSharedMemoryName, mapSize); // Gets a read/write buffer mapping the entire shared memory region. - final ByteBuffer buffer = mRankingMapFd.mapReadWrite(); + buffer = mRankingMapFd.mapReadWrite(); // Puts the ranking map into the shared memory region buffer. buffer.put(mapParcel.marshall(), 0, mapSize); // Protects the region from being written to, by setting it to be read-only. @@ -238,6 +232,12 @@ public class NotificationRankingUpdate implements Parcelable { throw new RuntimeException(e); } finally { mapParcel.recycle(); + // To prevent memory leaks, we can close the ranking map fd here. + // Because a reference to this still exists + if (buffer != null && mRankingMapFd != null) { + SharedMemory.unmap(buffer); + mRankingMapFd.close(); + } } } else { out.writeParcelable(mRankingMap, flags); @@ -247,7 +247,7 @@ public class NotificationRankingUpdate implements Parcelable { /** * @hide */ - public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR + public static final @NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR = new Parcelable.Creator<NotificationRankingUpdate>() { public NotificationRankingUpdate createFromParcel(Parcel parcel) { return new NotificationRankingUpdate(parcel); diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java index e5ad85cb526f..07367df7bc91 100644 --- a/core/java/android/service/notification/NotificationStats.java +++ b/core/java/android/service/notification/NotificationStats.java @@ -15,10 +15,12 @@ */ package android.service.notification; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.Flags; import android.app.RemoteInput; import android.os.Parcel; import android.os.Parcelable; @@ -36,6 +38,7 @@ public final class NotificationStats implements Parcelable { private boolean mSeen; private boolean mExpanded; private boolean mDirectReplied; + private boolean mSmartReplied; private boolean mSnoozed; private boolean mViewedSettings; private boolean mInteracted; @@ -125,6 +128,9 @@ public final class NotificationStats implements Parcelable { mSeen = in.readByte() != 0; mExpanded = in.readByte() != 0; mDirectReplied = in.readByte() != 0; + if (Flags.lifetimeExtensionRefactor()) { + mSmartReplied = in.readByte() != 0; + } mSnoozed = in.readByte() != 0; mViewedSettings = in.readByte() != 0; mInteracted = in.readByte() != 0; @@ -137,6 +143,9 @@ public final class NotificationStats implements Parcelable { dest.writeByte((byte) (mSeen ? 1 : 0)); dest.writeByte((byte) (mExpanded ? 1 : 0)); dest.writeByte((byte) (mDirectReplied ? 1 : 0)); + if (Flags.lifetimeExtensionRefactor()) { + dest.writeByte((byte) (mSmartReplied ? 1 : 0)); + } dest.writeByte((byte) (mSnoozed ? 1 : 0)); dest.writeByte((byte) (mViewedSettings ? 1 : 0)); dest.writeByte((byte) (mInteracted ? 1 : 0)); @@ -210,6 +219,23 @@ public final class NotificationStats implements Parcelable { } /** + * Returns whether the user has replied to a notification that has a smart reply at least once. + */ + @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public boolean hasSmartReplied() { + return mSmartReplied; + } + + /** + * Records that the user has replied to a notification that has a smart reply at least once. + */ + @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public void setSmartReplied() { + mSmartReplied = true; + mInteracted = true; + } + + /** * Returns whether the user has snoozed this notification at least once. */ public boolean hasSnoozed() { @@ -286,6 +312,9 @@ public final class NotificationStats implements Parcelable { if (mSeen != that.mSeen) return false; if (mExpanded != that.mExpanded) return false; if (mDirectReplied != that.mDirectReplied) return false; + if (Flags.lifetimeExtensionRefactor()) { + if (mSmartReplied != that.mSmartReplied) return false; + } if (mSnoozed != that.mSnoozed) return false; if (mViewedSettings != that.mViewedSettings) return false; if (mInteracted != that.mInteracted) return false; @@ -297,6 +326,9 @@ public final class NotificationStats implements Parcelable { int result = (mSeen ? 1 : 0); result = 31 * result + (mExpanded ? 1 : 0); result = 31 * result + (mDirectReplied ? 1 : 0); + if (Flags.lifetimeExtensionRefactor()) { + result = 31 * result + (mSmartReplied ? 1 : 0); + } result = 31 * result + (mSnoozed ? 1 : 0); result = 31 * result + (mViewedSettings ? 1 : 0); result = 31 * result + (mInteracted ? 1 : 0); @@ -311,6 +343,9 @@ public final class NotificationStats implements Parcelable { sb.append("mSeen=").append(mSeen); sb.append(", mExpanded=").append(mExpanded); sb.append(", mDirectReplied=").append(mDirectReplied); + if (Flags.lifetimeExtensionRefactor()) { + sb.append(", mSmartReplied=").append(mSmartReplied); + } sb.append(", mSnoozed=").append(mSnoozed); sb.append(", mViewedSettings=").append(mViewedSettings); sb.append(", mInteracted=").append(mInteracted); diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 0063d13cb3ab..886727ea43ef 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -490,16 +490,32 @@ public class TelephonyRegistryManager { /** * Notify changes to activity state changes on certain subscription. * + * @param subId for which data activity state changed. + * @param dataActivityType indicates the latest data activity type e.g. {@link + * TelephonyManager#DATA_ACTIVITY_IN} + */ + public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) { + try { + sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType); + } catch (RemoteException ex) { + // system process is dead + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Notify changes to activity state changes on certain subscription. + * * @param slotIndex for which data activity changed. Can be derived from subId except * when subId is invalid. * @param subId for which data activity state changed. - * @param dataActivityType indicates the latest data activity type e.g, {@link + * @param dataActivityType indicates the latest data activity type e.g. {@link * TelephonyManager#DATA_ACTIVITY_IN} */ public void notifyDataActivityChanged(int slotIndex, int subId, @DataActivityType int dataActivityType) { try { - sRegistry.notifyDataActivityForSubscriber(slotIndex, subId, dataActivityType); + sRegistry.notifyDataActivityForSubscriberWithSlot(slotIndex, subId, dataActivityType); } catch (RemoteException ex) { // system process is dead throw ex.rethrowFromSystemServer(); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 8befe8a2be85..cbbe7856178d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -503,9 +503,6 @@ public final class SurfaceControl implements Parcelable { // be dumped as additional context private static volatile boolean sDebugUsageAfterRelease = false; - static GlobalTransactionWrapper sGlobalTransaction; - static long sTransactionNestCount = 0; - private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced(SurfaceControl.class.getClassLoader(), nativeGetNativeSurfaceControlFinalizer()); @@ -859,33 +856,47 @@ public final class SurfaceControl implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"FRAME_RATE_SELECTION_STRATEGY_"}, - value = {FRAME_RATE_SELECTION_STRATEGY_SELF, - FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN}) + value = {FRAME_RATE_SELECTION_STRATEGY_PROPAGATE, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN, + FRAME_RATE_SELECTION_STRATEGY_SELF}) public @interface FrameRateSelectionStrategy {} // From window.h. Keep these in sync. /** * Default value. The layer uses its own frame rate specifications, assuming it has any - * specifications, instead of its parent's. + * specifications, instead of its parent's. If it does not have its own frame rate + * specifications, it will try to use its parent's. It will propagate its specifications to any + * descendants that do not have their own. + * * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor layer - * supersedes this behavior, meaning that this layer will inherit the frame rate specifications - * of that ancestor layer. + * supersedes this behavior, meaning that this layer will inherit frame rate specifications + * regardless of whether it has its own. * @hide */ - public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 0; + public static final int FRAME_RATE_SELECTION_STRATEGY_PROPAGATE = 0; /** * The layer's frame rate specifications will propagate to and override those of its descendant * layers. - * The layer with this strategy has the {@link #FRAME_RATE_SELECTION_STRATEGY_SELF} behavior - * for itself. This does mean that any parent or ancestor layer that also has the strategy - * {@link FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's + * + * The layer itself has the {@link #FRAME_RATE_SELECTION_STRATEGY_PROPAGATE} behavior. + * Thus, ancestor layer that also has the strategy + * {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's * frame rate specifications. * @hide */ public static final int FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1; /** + * The layer's frame rate specifications will not propagate to its descendant + * layers, even if the descendant layer has no frame rate specifications. + * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor + * layer supersedes this behavior. + * @hide + */ + public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 2; + + /** * Builder class for {@link SurfaceControl} objects. * * By default the surface will be hidden, and have "unset" bounds, meaning it can @@ -1576,54 +1587,30 @@ public final class SurfaceControl implements Parcelable { return mNativeObject != 0; } - /* - * set surface parameters. - * needs to be inside open/closeTransaction block - */ - /** start a transaction * @hide - */ - @UnsupportedAppUsage - public static void openTransaction() { - synchronized (SurfaceControl.class) { - if (sGlobalTransaction == null) { - sGlobalTransaction = new GlobalTransactionWrapper(); - } - synchronized(SurfaceControl.class) { - sTransactionNestCount++; - } - } - } - - /** - * Merge the supplied transaction in to the deprecated "global" transaction. - * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}. - * <p> - * This is a utility for interop with legacy-code and will go away with the Global Transaction. - * @hide + * @deprecated Use regular Transaction instead. */ @Deprecated - public static void mergeToGlobalTransaction(Transaction t) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.merge(t); - } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM, + publicAlternatives = "Use {@code SurfaceControl.Transaction} instead", + trackingBug = 247078497) + public static void openTransaction() { + // TODO(b/247078497): It was used for global transaction (all usages are removed). + // Keep the method declaration to avoid breaking reference from legacy access. } /** end a transaction * @hide + * @deprecated Use regular Transaction instead. */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM, + publicAlternatives = "Use {@code SurfaceControl.Transaction} instead", + trackingBug = 247078497) public static void closeTransaction() { - synchronized(SurfaceControl.class) { - if (sTransactionNestCount == 0) { - Log.e(TAG, - "Call to SurfaceControl.closeTransaction without matching openTransaction"); - } else if (--sTransactionNestCount > 0) { - return; - } - sGlobalTransaction.applyGlobalTransaction(false); - } + // TODO(b/247078497): It was used for global transaction (all usages are removed). + // Keep the method declaration to avoid breaking reference from legacy access. } /** @@ -4499,39 +4486,6 @@ public final class SurfaceControl implements Parcelable { } /** - * As part of eliminating usage of the global Transaction we expose - * a SurfaceControl.getGlobalTransaction function. However calling - * apply on this global transaction (rather than using closeTransaction) - * would be very dangerous. So for the global transaction we use this - * subclass of Transaction where the normal apply throws an exception. - */ - private static class GlobalTransactionWrapper extends SurfaceControl.Transaction { - void applyGlobalTransaction(boolean sync) { - applyResizedSurfaces(); - notifyReparentedSurfaces(); - nativeApplyTransaction(mNativeObject, sync, /*oneWay*/ false); - } - - @Override - public void apply(boolean sync) { - throw new RuntimeException("Global transaction must be applied from closeTransaction"); - } - } - - /** - * This is a refactoring utility function to enable lower levels of code to be refactored - * from using the global transaction (and instead use a passed in Transaction) without - * having to refactor the higher levels at the same time. - * The returned global transaction can't be applied, it must be applied from closeTransaction - * Unless you are working on removing Global Transaction usage in the WindowManager, this - * probably isn't a good function to use. - * @hide - */ - public static Transaction getGlobalTransaction() { - return sGlobalTransaction; - } - - /** * @hide */ public void resize(int w, int h) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d591f896d99a..d58c07d96d49 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -33018,7 +33018,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private float getSizePercentage() { - if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) { + if (mResources == null || getVisibility() != VISIBLE) { return 0; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7a6c2929c706..cac5387116a1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -991,7 +991,7 @@ public final class ViewRootImpl implements ViewParent, // for idleness handling. private boolean mHasIdledMessage = false; // time for touch boost period. - private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500; + private static final int FRAME_RATE_TOUCH_BOOST_TIME = 3000; // time for checking idle status periodically. private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500; // time for revaluating the idle status before lowering the frame rate. diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl index a11c6d0ce956..a404bd6f8c97 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl @@ -54,7 +54,7 @@ oneway interface IWindowMagnificationConnection { * @param displayId the logical display id. * @param scale magnification scale. */ - void setScale(int displayId, float scale); + void setScaleForWindowMagnification(int displayId, float scale); /** * Disables window magnification on specified display with animation. diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl index e17773159ec5..c6bd20cec07d 100644 --- a/core/java/android/webkit/IWebViewUpdateService.aidl +++ b/core/java/android/webkit/IWebViewUpdateService.aidl @@ -79,4 +79,9 @@ interface IWebViewUpdateService { * Used by Settings to enable/disable multiprocess. */ void enableMultiProcess(boolean enable); + + /** + * Used by Settings to get the default WebView package. + */ + WebViewProviderInfo getDefaultWebViewPackage(); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a919c0079cea..1acebf4df590 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1054,8 +1054,7 @@ public class RemoteViews implements Parcelable, Filter { } private class SetRemoteCollectionItemListAdapterAction extends Action { - @NonNull - private CompletableFuture<RemoteCollectionItems> mItemsFuture; + private @Nullable RemoteCollectionItems mItems; final Intent mServiceIntent; int mIntentId = -1; boolean mIsReplacedIntoAction = false; @@ -1064,92 +1063,46 @@ public class RemoteViews implements Parcelable, Filter { @NonNull RemoteCollectionItems items) { mViewId = id; items.setHierarchyRootData(getHierarchyRootData()); - mItemsFuture = CompletableFuture.completedFuture(items); + mItems = items; mServiceIntent = null; } SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) { mViewId = id; - mItemsFuture = getItemsFutureFromIntentWithTimeout(intent); - setHierarchyRootData(getHierarchyRootData()); + mItems = null; mServiceIntent = intent; } - private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( - Intent intent) { - if (intent == null) { - Log.e(LOG_TAG, "Null intent received when generating adapter future"); - return CompletableFuture.completedFuture(new RemoteCollectionItems - .Builder().build()); - } - - final Context context = ActivityThread.currentApplication(); - final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); - - context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), - result.defaultExecutor(), new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, - IBinder iBinder) { - RemoteCollectionItems items; - try { - items = IRemoteViewsFactory.Stub.asInterface(iBinder) - .getRemoteCollectionItems(); - } catch (RemoteException re) { - items = new RemoteCollectionItems.Builder().build(); - Log.e(LOG_TAG, "Error getting collection items from the factory", - re); - } finally { - context.unbindService(this); - } - - result.complete(items); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { } - }); - - result.completeOnTimeout( - new RemoteCollectionItems.Builder().build(), - MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS); - - return result; - } - SetRemoteCollectionItemListAdapterAction(Parcel parcel) { mViewId = parcel.readInt(); mIntentId = parcel.readInt(); - mItemsFuture = CompletableFuture.completedFuture(mIntentId != -1 - ? null - : new RemoteCollectionItems(parcel, getHierarchyRootData())); mServiceIntent = parcel.readTypedObject(Intent.CREATOR); + mItems = mServiceIntent != null + ? null + : new RemoteCollectionItems(parcel, getHierarchyRootData()); } @Override public void setHierarchyRootData(HierarchyRootData rootData) { - if (mIntentId == -1) { - mItemsFuture = mItemsFuture - .thenApply(rc -> { - rc.setHierarchyRootData(rootData); - return rc; - }); + if (mItems != null) { + mItems.setHierarchyRootData(rootData); return; } - // Set the root data for items in the cache instead - mCollectionCache.setHierarchyDataForId(mIntentId, rootData); + if (mIntentId != -1) { + // Set the root data for items in the cache instead + mCollectionCache.setHierarchyDataForId(mIntentId, rootData); + } } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mViewId); dest.writeInt(mIntentId); - if (mIntentId == -1) { - RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture); - items.writeToParcel(dest, flags, /* attached= */ true); - } dest.writeTypedObject(mServiceIntent, flags); + if (mItems != null) { + mItems.writeToParcel(dest, flags, /* attached= */ true); + } } @Override @@ -1159,7 +1112,9 @@ public class RemoteViews implements Parcelable, Filter { if (target == null) return; RemoteCollectionItems items = mIntentId == -1 - ? getCollectionItemsFromFuture(mItemsFuture) + ? mItems == null + ? new RemoteCollectionItems.Builder().build() + : mItems : mCollectionCache.getItemsForId(mIntentId); // Ensure that we are applying to an AppWidget root @@ -1216,51 +1171,32 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture); - items.visitUris(visitor); - } - } + if (mIntentId != -1 || mItems == null) { + return; + } - private static RemoteCollectionItems getCollectionItemsFromFuture( - CompletableFuture<RemoteCollectionItems> itemsFuture) { - RemoteCollectionItems items; - try { - items = itemsFuture.get(); - } catch (Exception e) { - Log.e(LOG_TAG, "Error getting collection items from future", e); - items = new RemoteCollectionItems.Builder().build(); + mItems.visitUris(visitor); } - - return items; } /** * @hide */ - public void collectAllIntents() { - mCollectionCache.collectAllIntentsNoComplete(this); + public CompletableFuture<Void> collectAllIntents() { + return mCollectionCache.collectAllIntentsNoComplete(this); } private class RemoteCollectionCache { private SparseArray<String> mIdToUriMapping = new SparseArray<>(); private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>(); - // We don't put this into the parcel - private HashMap<String, CompletableFuture<RemoteCollectionItems>> mTempUriToFutureMapping = - new HashMap<>(); - RemoteCollectionCache() { } RemoteCollectionCache(RemoteCollectionCache src) { - boolean isWaitingCache = src.mTempUriToFutureMapping.size() != 0; for (int i = 0; i < src.mIdToUriMapping.size(); i++) { String uri = src.mIdToUriMapping.valueAt(i); mIdToUriMapping.put(src.mIdToUriMapping.keyAt(i), uri); - if (isWaitingCache) { - mTempUriToFutureMapping.put(uri, src.mTempUriToFutureMapping.get(uri)); - } else { - mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri)); - } + mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri)); } } @@ -1281,14 +1217,8 @@ public class RemoteViews implements Parcelable, Filter { void setHierarchyDataForId(int intentId, HierarchyRootData data) { String uri = mIdToUriMapping.get(intentId); - if (mTempUriToFutureMapping.get(uri) != null) { - CompletableFuture<RemoteCollectionItems> itemsFuture = - mTempUriToFutureMapping.get(uri); - mTempUriToFutureMapping.put(uri, itemsFuture.thenApply(rc -> { - rc.setHierarchyRootData(data); - return rc; - })); - + if (mUriToCollectionMapping.get(uri) == null) { + Log.e(LOG_TAG, "Error setting hierarchy data for id=" + intentId); return; } @@ -1301,14 +1231,17 @@ public class RemoteViews implements Parcelable, Filter { return mUriToCollectionMapping.get(uri); } - void collectAllIntentsNoComplete(@NonNull RemoteViews inViews) { + CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) { + CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null); if (inViews.hasSizedRemoteViews()) { for (RemoteViews remoteViews : inViews.mSizedRemoteViews) { - remoteViews.collectAllIntents(); + collectionFuture = CompletableFuture.allOf(collectionFuture, + collectAllIntentsNoComplete(remoteViews)); } } else if (inViews.hasLandscapeAndPortraitLayouts()) { - inViews.mLandscape.collectAllIntents(); - inViews.mPortrait.collectAllIntents(); + collectionFuture = CompletableFuture.allOf( + collectAllIntentsNoComplete(inViews.mLandscape), + collectAllIntentsNoComplete(inViews.mPortrait)); } else if (inViews.mActions != null) { for (Action action : inViews.mActions) { if (action instanceof SetRemoteCollectionItemListAdapterAction rca) { @@ -1318,40 +1251,95 @@ public class RemoteViews implements Parcelable, Filter { } if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) { - String uri = mIdToUriMapping.get(rca.mIntentId); - mTempUriToFutureMapping.put(uri, rca.mItemsFuture); - rca.mItemsFuture = CompletableFuture.completedFuture(null); + final String uri = mIdToUriMapping.get(rca.mIntentId); + collectionFuture = CompletableFuture.allOf(collectionFuture, + getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) + .thenAccept(rc -> { + rc.setHierarchyRootData(getHierarchyRootData()); + mUriToCollectionMapping.put(uri, rc); + })); + rca.mItems = null; continue; } // Differentiate between the normal collection actions and the ones with // intents. if (rca.mServiceIntent != null) { - String uri = rca.mServiceIntent.toUri(0); + final String uri = rca.mServiceIntent.toUri(0); int index = mIdToUriMapping.indexOfValue(uri); if (index == -1) { int newIntentId = mIdToUriMapping.size(); rca.mIntentId = newIntentId; mIdToUriMapping.put(newIntentId, uri); - // mUriToIntentMapping.put(uri, mServiceIntent); - mTempUriToFutureMapping.put(uri, rca.mItemsFuture); } else { rca.mIntentId = mIdToUriMapping.keyAt(index); + rca.mItems = null; + continue; } - rca.mItemsFuture = CompletableFuture.completedFuture(null); + collectionFuture = CompletableFuture.allOf(collectionFuture, + getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) + .thenAccept(rc -> { + rc.setHierarchyRootData(getHierarchyRootData()); + mUriToCollectionMapping.put(uri, rc); + })); + rca.mItems = null; } else { - RemoteCollectionItems items = getCollectionItemsFromFuture( - rca.mItemsFuture); - for (RemoteViews views : items.mViews) { - views.collectAllIntents(); + for (RemoteViews views : rca.mItems.mViews) { + collectionFuture = CompletableFuture.allOf(collectionFuture, + collectAllIntentsNoComplete(views)); } } } else if (action instanceof ViewGroupActionAdd vgaa && vgaa.mNestedViews != null) { - vgaa.mNestedViews.collectAllIntents(); + collectionFuture = CompletableFuture.allOf(collectionFuture, + collectAllIntentsNoComplete(vgaa.mNestedViews)); } } } + + return collectionFuture; + } + + private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( + Intent intent) { + if (intent == null) { + Log.e(LOG_TAG, "Null intent received when generating adapter future"); + return CompletableFuture.completedFuture(new RemoteCollectionItems + .Builder().build()); + } + + final Context context = ActivityThread.currentApplication(); + final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); + + context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), + result.defaultExecutor(), new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, + IBinder iBinder) { + RemoteCollectionItems items; + try { + items = IRemoteViewsFactory.Stub.asInterface(iBinder) + .getRemoteCollectionItems(); + } catch (RemoteException re) { + items = new RemoteCollectionItems.Builder().build(); + Log.e(LOG_TAG, "Error getting collection items from the factory", + re); + } finally { + context.unbindService(this); + } + + result.complete(items); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { } + }); + + result.completeOnTimeout( + new RemoteCollectionItems.Builder().build(), + MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS); + + return result; } public void writeToParcel(Parcel out, int flags) { @@ -1360,10 +1348,7 @@ public class RemoteViews implements Parcelable, Filter { out.writeInt(mIdToUriMapping.keyAt(i)); String intentUri = mIdToUriMapping.valueAt(i); out.writeString8(intentUri); - RemoteCollectionItems items = mTempUriToFutureMapping.get(intentUri) != null - ? getCollectionItemsFromFuture(mTempUriToFutureMapping.get(intentUri)) - : mUriToCollectionMapping.get(intentUri); - items.writeToParcel(out, flags, true); + mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true); } } } diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java index 5b0d8d1233c6..cc2329fc47cb 100644 --- a/core/java/android/window/SystemPerformanceHinter.java +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -20,7 +20,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; -import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE; import android.annotation.IntDef; import android.annotation.NonNull; @@ -303,7 +303,7 @@ public class SystemPerformanceHinter { SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay( session.displayId); mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, - FRAME_RATE_SELECTION_STRATEGY_SELF); + FRAME_RATE_SELECTION_STRATEGY_PROPAGATE); // smoothSwitchOnly is false to request a higher framerate, even if it means switching // the display mode will cause would jank on non-VRR devices because keeping a lower // refresh rate would mean a poorer user experience. diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 4e0f9a51c0a0..0ec9ffe6390b 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -108,6 +108,18 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; + /** + * Creates a decor surface in the parent Task of the TaskFragment. The created decor surface + * will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED} + * event callback. + */ + public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14; + + /** + * Removes the decor surface in the parent Task of the TaskFragment. + */ + public static final int OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE = 15; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -124,6 +136,8 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ISOLATED_NAVIGATION, OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, OP_TYPE_REORDER_TO_TOP_OF_TASK, + OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE, + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java index e6eeca4b4801..a77c23475c60 100644 --- a/core/java/android/window/TaskFragmentParentInfo.java +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -22,6 +22,9 @@ import android.app.WindowConfiguration; import android.content.res.Configuration; import android.os.Parcel; import android.os.Parcelable; +import android.view.SurfaceControl; + +import java.util.Objects; /** * The information about the parent Task of a particular TaskFragment @@ -37,12 +40,15 @@ public class TaskFragmentParentInfo implements Parcelable { private final boolean mHasDirectActivity; + @Nullable private final SurfaceControl mDecorSurface; + public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, - boolean visible, boolean hasDirectActivity) { + boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) { mConfiguration.setTo(configuration); mDisplayId = displayId; mVisible = visible; mHasDirectActivity = hasDirectActivity; + mDecorSurface = decorSurface; } public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { @@ -50,6 +56,7 @@ public class TaskFragmentParentInfo implements Parcelable { mDisplayId = info.mDisplayId; mVisible = info.mVisible; mHasDirectActivity = info.mHasDirectActivity; + mDecorSurface = info.mDecorSurface; } /** The {@link Configuration} of the parent Task */ @@ -92,7 +99,13 @@ public class TaskFragmentParentInfo implements Parcelable { return false; } return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId - && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity; + && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity + && mDecorSurface == that.mDecorSurface; + } + + @Nullable + public SurfaceControl getDecorSurface() { + return mDecorSurface; } @WindowConfiguration.WindowingMode @@ -107,6 +120,7 @@ public class TaskFragmentParentInfo implements Parcelable { + ", displayId=" + mDisplayId + ", visible=" + mVisible + ", hasDirectActivity=" + mHasDirectActivity + + ", decorSurface=" + mDecorSurface + "}"; } @@ -128,7 +142,8 @@ public class TaskFragmentParentInfo implements Parcelable { return mConfiguration.equals(that.mConfiguration) && mDisplayId == that.mDisplayId && mVisible == that.mVisible - && mHasDirectActivity == that.mHasDirectActivity; + && mHasDirectActivity == that.mHasDirectActivity + && mDecorSurface == that.mDecorSurface; } @Override @@ -137,6 +152,7 @@ public class TaskFragmentParentInfo implements Parcelable { result = 31 * result + mDisplayId; result = 31 * result + (mVisible ? 1 : 0); result = 31 * result + (mHasDirectActivity ? 1 : 0); + result = 31 * result + Objects.hashCode(mDecorSurface); return result; } @@ -146,6 +162,7 @@ public class TaskFragmentParentInfo implements Parcelable { dest.writeInt(mDisplayId); dest.writeBoolean(mVisible); dest.writeBoolean(mHasDirectActivity); + dest.writeTypedObject(mDecorSurface, flags); } private TaskFragmentParentInfo(Parcel in) { @@ -153,6 +170,7 @@ public class TaskFragmentParentInfo implements Parcelable { mDisplayId = in.readInt(); mVisible = in.readBoolean(); mHasDirectActivity = in.readBoolean(); + mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR); } public static final Creator<TaskFragmentParentInfo> CREATOR = diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 7c4252e7aa5d..6b074a610818 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -33,6 +33,7 @@ import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL; @@ -131,6 +132,18 @@ public final class AccessibilityStatsLogUtils { } /** + * Logs magnification that is assigned to the two finger triple tap shortcut. Calls this when + * triggering the magnification two finger triple tap shortcut. + */ + public static void logMagnificationTwoFingerTripleTap(boolean enabled) { + FrameworkStatsLog.write(FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED, + MAGNIFICATION_COMPONENT_NAME.flattenToString(), + // jean update + ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP, + convertToLoggingServiceStatus(enabled)); + } + + /** * Logs accessibility feature name that is assigned to the long pressed accessibility button * shortcut. Calls this when clicking the long pressed accessibility button shortcut. * diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java index f5fe12eb66c0..e55c64199f45 100644 --- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java +++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java @@ -37,22 +37,11 @@ public class RefreshRateSettingsUtils { * @return The highest refresh rate */ public static float findHighestRefreshRateForDefaultDisplay(Context context) { - return findHighestRefreshRate(context, Display.DEFAULT_DISPLAY); - } - - /** - * Find the highest refresh rate among all the modes of the specified display. - * - * @param context The context - * @param displayId The display ID - * @return The highest refresh rate - */ - public static float findHighestRefreshRate(Context context, int displayId) { final DisplayManager dm = context.getSystemService(DisplayManager.class); - final Display display = dm.getDisplay(displayId); + final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); if (display == null) { - Log.w(TAG, "No valid display device with ID = " + displayId); + Log.w(TAG, "No valid default display device"); return DEFAULT_REFRESH_RATE; } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index dadeb2b74c7d..aab22421b334 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -60,7 +60,8 @@ interface ITelephonyRegistry { @UnsupportedAppUsage(maxTargetSdk = 28) void notifyCallForwardingChanged(boolean cfi); void notifyCallForwardingChangedForSubscriber(in int subId, boolean cfi); - void notifyDataActivityForSubscriber(int phoneId, int subId, int state); + void notifyDataActivityForSubscriber(int subId, int state); + void notifyDataActivityForSubscriberWithSlot(int phoneId, int subId, int state); void notifyDataConnectionForSubscriber( int phoneId, int subId, in PreciseDataConnectionState preciseState); // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client. diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 440a33244a62..f365dbb1d46a 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -462,9 +462,4 @@ cc_library_shared_for_libandroid_runtime { ], }, }, - - // Workaround Clang LTO crash. - lto: { - never: true, - }, } diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto index 2b74220a1471..11b367b51ae2 100644 --- a/core/proto/android/os/batteryusagestats.proto +++ b/core/proto/android/os/batteryusagestats.proto @@ -92,8 +92,24 @@ message BatteryUsageStatsAtomsProto { message UidBatteryConsumer { optional int32 uid = 1; optional BatteryConsumerData battery_consumer_data = 2; - optional int64 time_in_foreground_millis = 3; - optional int64 time_in_background_millis = 4; + // DEPRECATED Use time_in_state instead. + optional int64 time_in_foreground_millis = 3 [deprecated = true]; + // DEPRECATED Use time_in_state instead. + optional int64 time_in_background_millis = 4 [deprecated = true]; + + message TimeInState { + enum ProcessState { + UNSPECIFIED = 0; + FOREGROUND = 1; + BACKGROUND = 2; + FOREGROUND_SERVICE = 3; + } + + optional ProcessState process_state = 1; + optional int64 time_in_state_millis = 2; + } + + repeated TimeInState time_in_state = 5; } repeated UidBatteryConsumer uid_battery_consumers = 5; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4d208c6ed30a..002164011e84 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7817,6 +7817,13 @@ <permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" android:protectionLevel="signature" /> + <!-- @SystemApi Allows an app to track all preparations for a complete factory reset. + <p>Protection level: signature|privileged + @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis") + @hide --> + <permission android:name="android.permission.PREPARE_FACTORY_RESET" + android:protectionLevel="signature|privileged" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/OWNERS b/core/res/OWNERS index f24c3f59155a..332ad2a82d38 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -47,6 +47,10 @@ per-file res/values/config_device_idle.xml = file:/apex/jobscheduler/OWNERS # Wear per-file res/*-watch/* = file:/WEAR_OWNERS +# Peformance +per-file res/values/config.xml = file:/PERFORMANCE_OWNERS +per-file res/values/symbols.xml = file:/PERFORMANCE_OWNERS + # PowerProfile per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6cd6eb4b8df9..1d4e01a95591 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1522,6 +1522,10 @@ config_screenBrightnessSettingDefaultFloat instead --> <integer name="config_screenBrightnessSettingDefault">102</integer> + <!-- Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode. + The value must be in the range [0, 255]. --> + <integer name="config_screenBrightnessCapForWearBedtimeMode">20</integer> + <!-- Minimum screen brightness setting allowed by power manager. -2 is invalid so setting will resort to int value specified above. Set this to 0.0 to allow screen to go to minimal brightness. @@ -4336,6 +4340,9 @@ <!-- True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> + <!-- Bytes that the PinnerService will pin for WebView --> + <integer name="config_pinnerWebviewPinBytes">0</integer> + <!-- Number of days preloaded file cache should be preserved on a device before it can be deleted --> <integer name="config_keepPreloadsMinDays">7</integer> @@ -4986,6 +4993,11 @@ <!-- Component name for the default module metadata provider on this device --> <string name="config_defaultModuleMetadataProvider" translatable="false">com.android.modulemetadata</string> + <!-- Packages that contain a security state. + {@link SecurityStateManager#getGlobalSecurityState} will read and report the state/version + of these packages. --> + <string-array name="config_securityStatePackages" translatable="false" /> + <!-- Package name for the default Health Connect app. OEMs can set this with their own health app package name to define a default app with high priority for the app to store the health data. If set the app always has priority of 1 diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml index bc9ca3decec3..7a707c0a6cdc 100644 --- a/core/res/res/values/config_device_idle.xml +++ b/core/res/res/values/config_device_idle.xml @@ -28,7 +28,7 @@ <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">240000</integer> + <integer name="device_idle_light_after_inactive_to_ms">60000</integer> <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_TIMEOUT --> <integer name="device_idle_light_idle_to_ms">300000</integer> @@ -43,7 +43,7 @@ <item name="device_idle_light_idle_factor" format="float" type="integer">2.0</item> <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_INCREASE_LINEARLY --> - <bool name="device_idle_light_idle_increase_linearly">false</bool> + <bool name="device_idle_light_idle_increase_linearly">true</bool> <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS --> <integer name="device_idle_light_idle_linear_increase_factor_ms">300000</integer> @@ -52,7 +52,7 @@ <integer name="device_idle_light_idle_flex_linear_increase_factor_ms">60000</integer> <!-- Default for DeviceIdleController.Constants.LIGHT_MAX_IDLE_TIMEOUT --> - <integer name="device_idle_light_max_idle_to_ms">900000</integer> + <integer name="device_idle_light_max_idle_to_ms">1800000</integer> <!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET --> <integer name="device_idle_light_idle_maintenance_min_budget_ms">60000</integer> @@ -67,13 +67,13 @@ <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> + <integer name="device_idle_inactive_to_ms">60000</integer> <!-- Default for DeviceIdleController.Constants.SENSING_TIMEOUT --> - <integer name="device_idle_sensing_to_ms">240000</integer> + <integer name="device_idle_sensing_to_ms">30000</integer> <!-- Default for DeviceIdleController.Constants.LOCATING_TIMEOUT --> - <integer name="device_idle_locating_to_ms">30000</integer> + <integer name="device_idle_locating_to_ms">15000</integer> <!-- Default for DeviceIdleController.Constants.LOCATION_ACCURACY --> <item name="device_idle_location_accuracy" format="float" type="integer">20.0</item> @@ -85,7 +85,7 @@ <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> + <integer name="device_idle_idle_after_inactive_to_ms">60000</integer> <!-- Default for DeviceIdleController.Constants.IDLE_PENDING_TIMEOUT --> <integer name="device_idle_idle_pending_to_ms">300000</integer> @@ -100,7 +100,7 @@ <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> + <integer name="device_idle_idle_to_ms">900000</integer> <!-- Default for DeviceIdleController.Constants.MAX_IDLE_TIMEOUT --> <integer name="device_idle_max_idle_to_ms">21600000</integer> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 38f1f6756d17..4b0fa4ba5173 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1274,6 +1274,7 @@ <java-symbol type="array" name="policy_exempt_apps" /> <java-symbol type="array" name="vendor_policy_exempt_apps" /> <java-symbol type="array" name="cloneable_apps" /> + <java-symbol type="array" name="config_securityStatePackages" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="default_lock_wallpaper" /> @@ -2078,6 +2079,7 @@ <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" /> <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" /> <java-symbol type="integer" name="config_screenBrightnessSettingDefault" /> + <java-symbol type="integer" name="config_screenBrightnessCapForWearBedtimeMode" /> <java-symbol type="dimen" name="config_screenBrightnessSettingMinimumFloat" /> <java-symbol type="dimen" name="config_screenBrightnessSettingMaximumFloat" /> <java-symbol type="dimen" name="config_screenBrightnessSettingDefaultFloat" /> @@ -3385,6 +3387,7 @@ <java-symbol type="bool" name="config_pinnerCameraApp" /> <java-symbol type="bool" name="config_pinnerHomeApp" /> <java-symbol type="bool" name="config_pinnerAssistantApp" /> + <java-symbol type="integer" name="config_pinnerWebviewPinBytes" /> <java-symbol type="string" name="config_doubleTouchGestureEnableFile" /> diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java index aaaa3c7740c5..ac1f7d0e345f 100644 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java +++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java @@ -48,6 +48,11 @@ public class BatteryUsageStatsPulledTest { private static final int UID_1 = 2000; private static final int UID_2 = 3000; private static final int UID_3 = 4000; + private static final int[] UID_USAGE_TIME_PROCESS_STATES = { + BatteryConsumer.PROCESS_STATE_FOREGROUND, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE + }; @Test public void testGetStatsProto() { @@ -195,6 +200,20 @@ public class BatteryUsageStatsPulledTest { assertEquals("For uid " + uid, uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND), uidConsumerProto.timeInBackgroundMillis); + for (int processState : UID_USAGE_TIME_PROCESS_STATES) { + final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState); + if (timeInStateMillis <= 0) { + continue; + } + assertEquals("For uid " + uid + ", process state " + processState, + timeInStateMillis, + Arrays.stream(uidConsumerProto.timeInState) + .filter(timeInState -> timeInState.processState == processState) + .findFirst() + .orElseThrow() + .timeInStateMillis); + } + if (expectNullBatteryConsumerData) { assertNull("For uid " + uid, uidConsumerProto.batteryConsumerData); } else { @@ -250,8 +269,8 @@ public class BatteryUsageStatsPulledTest { final UidBatteryConsumer.Builder uidBuilder = builder .getOrCreateUidBatteryConsumerBuilder(UID_0) .setPackageWithHighestDrain("myPackage0") - .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000) - .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000) + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000) + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 2000) .setConsumedPower( BatteryConsumer.POWER_COMPONENT_SCREEN, 300) .setConsumedPower( @@ -285,7 +304,7 @@ public class BatteryUsageStatsPulledTest { builder.getOrCreateUidBatteryConsumerBuilder(UID_1) .setPackageWithHighestDrain("myPackage1") - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1234); + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234); builder.getOrCreateUidBatteryConsumerBuilder(UID_2) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, @@ -331,8 +350,10 @@ public class BatteryUsageStatsPulledTest { // significantly larger than 50 Kb for (int i = 0; i < 3000; i++) { builder.getOrCreateUidBatteryConsumerBuilder(i) - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1 * 60 * 1000) - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 2 * 60 * 1000) + .setTimeInProcessStateMs( + BatteryConsumer.PROCESS_STATE_FOREGROUND, 1 * 60 * 1000) + .setTimeInProcessStateMs( + BatteryConsumer.PROCESS_STATE_BACKGROUND, 2 * 60 * 1000) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40); } @@ -340,16 +361,16 @@ public class BatteryUsageStatsPulledTest { // Add a UID with much larger battery footprint final int largeConsumerUid = 3001; builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerUid) - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 10 * 60 * 1000) - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 20 * 60 * 1000) + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 10 * 60 * 1000) + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 20 * 60 * 1000) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400); // Add a UID with much larger usage duration final int highUsageUid = 3002; builder.getOrCreateUidBatteryConsumerBuilder(highUsageUid) - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 60 * 60 * 1000) - .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 120 * 60 * 1000) + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 60 * 60 * 1000) + .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 120 * 60 * 1000) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4); diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index 87c167c0df07..4a9cb7180a3f 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -83,6 +83,12 @@ public class ResourcesManagerTest extends TestCase { } @Override + protected AssetManager createAssetManager(@NonNull final ResourcesKey key, + ResourcesManager.ApkAssetsSupplier apkSupplier) { + return createAssetManager(key); + } + + @Override protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) { return mDisplayMetricsMap.get(displayId); } @@ -100,7 +106,7 @@ public class ResourcesManagerTest extends TestCase { null, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); - assertSame(resources, newResources); + assertSame(resources.getImpl(), newResources.getImpl()); } @SmallTest diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java index 0855268411eb..1bdb006c3465 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java @@ -472,6 +472,9 @@ public class NotificationRankingUpdateTest { NotificationRankingUpdate nru = generateUpdate(getContext()); Parcel parcel = Parcel.obtain(); nru.writeToParcel(parcel, 0); + if (Flags.rankingUpdateAshmem()) { + assertTrue(nru.isFdNotNullAndClosed()); + } parcel.setDataPosition(0); NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel); // The rankingUpdate file descriptor is only non-null in the new path. diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 6229530dc33f..3147eac30705 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -21,7 +21,7 @@ import static android.os.PerformanceHintManager.Session.CPU_LOAD_UP; import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; -import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE; import static android.window.SystemPerformanceHinter.HINT_ADPF; import static android.window.SystemPerformanceHinter.HINT_ALL; import static android.window.SystemPerformanceHinter.HINT_SF_EARLY_WAKEUP; @@ -170,7 +170,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -262,7 +262,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF and perf manager to clean up verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -283,7 +283,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF and perf manager to clean up verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -334,7 +334,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF and perf manager to clean up verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -385,7 +385,7 @@ public class SystemPerformanceHinterTests { session1.close(); verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -410,7 +410,7 @@ public class SystemPerformanceHinterTests { anyInt()); verify(mTransaction).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mSecondaryDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index d36ac3914ff3..2237ba1924db 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -529,6 +529,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/ActivityStarter.java" }, + "-1582845629": { + "message": "Starting animation on %s", + "level": "VERBOSE", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java" + }, "-1575977269": { "message": "Skipping %s: mismatch root %s", "level": "DEBUG", @@ -925,6 +931,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-1243510456": { + "message": "Dim animation requested: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java" + }, "-1237827119": { "message": "Schedule remove starting %s startingWindow=%s animate=%b Callers=%s", "level": "VERBOSE", @@ -1171,6 +1183,12 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, + "-1028213464": { + "message": "%s skipping animation and directly setting alpha=%f, blur=%d", + "level": "DEBUG", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java" + }, "-1022146708": { "message": "Skipping %s: mismatch activity type", "level": "DEBUG", @@ -1795,12 +1813,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-504637678": { - "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d", - "level": "VERBOSE", - "group": "WM_DEBUG_DIMMER", - "at": "com\/android\/server\/wm\/SmoothDimmer.java" - }, "-503656156": { "message": "Update process config of %s to new config %s", "level": "VERBOSE", @@ -4027,12 +4039,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "1620751818": { - "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d", - "level": "DEBUG", - "group": "WM_DEBUG_DIMMER", - "at": "com\/android\/server\/wm\/SmoothDimmer.java" - }, "1621562070": { "message": " startWCT=%s", "level": "VERBOSE", diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index 5e16bcee1a0e..dd703f5eefb9 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -33,7 +33,6 @@ import android.system.keystore2.ResponseCode; import android.util.Log; import java.util.Calendar; -import java.util.Objects; /** * @hide This should not be made public in its present form because it @@ -139,13 +138,25 @@ public class KeyStore2 { return new KeyStore2(); } + /** + * Gets the {@link IKeystoreService} that should be started in early_hal in Android. + * + * @throws IllegalStateException if the KeystoreService is not available or has not + * been initialized when called. This is a state that should not happen and indicates + * and error somewhere in the stack or with the calling processes access permissions. + */ @NonNull private synchronized IKeystoreService getService(boolean retryLookup) { if (mBinder == null || retryLookup) { mBinder = IKeystoreService.Stub.asInterface(ServiceManager - .getService(KEYSTORE2_SERVICE_NAME)); - Binder.allowBlocking(mBinder.asBinder()); + .getService(KEYSTORE2_SERVICE_NAME)); + } + if (mBinder == null) { + throw new IllegalStateException( + "Could not connect to Keystore service. Keystore may have crashed or not been" + + " initialized"); } - return Objects.requireNonNull(mBinder); + Binder.allowBlocking(mBinder.asBinder()); + return mBinder; } void delete(KeyDescriptor descriptor) throws KeyStoreException { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 50cfd941adb3..4c2433fab2f8 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -443,7 +443,8 @@ public class OverlayPresentationTest { assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); mSplitController.updateOverlayContainer(mTransaction, overlayContainer); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 02031a67e7e3..8c274a26177d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -1139,7 +1139,8 @@ public class SplitControllerTest { public void testOnTransactionReady_taskFragmentParentInfoChanged() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */); transaction.addChange(new TaskFragmentTransaction.Change( TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(TASK_ID) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index e56c8ab686e7..7b77235f66f7 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -79,14 +79,16 @@ public class TaskContainerTest { configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertEquals(WINDOWING_MODE_FREEFORM, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); @@ -106,13 +108,15 @@ public class TaskContainerTest { configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertFalse(taskContainer.isInPictureInPicture()); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertTrue(taskContainer.isInPictureInPicture()); } 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 f5b877a70b84..a3eb429b1d7e 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 @@ -412,6 +412,23 @@ public class BubbleExpandedView extends LinearLayout { setLayoutDirection(LAYOUT_DIRECTION_LOCALE); } + + /** Updates the width of the task view if it changed. */ + void updateTaskViewContentWidth() { + if (mTaskView != null) { + int width = getContentWidth(); + if (mTaskView.getWidth() != width) { + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(width, MATCH_PARENT); + mTaskView.setLayoutParams(lp); + } + } + } + + private int getContentWidth() { + boolean isStackOnLeft = mPositioner.isStackOnLeft(mStackView.getStackPosition()); + return mPositioner.getTaskViewContentWidth(isStackOnLeft); + } + /** * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need * to be called after view inflate. @@ -438,7 +455,12 @@ public class BubbleExpandedView extends LinearLayout { mController.getTaskViewTransitions(), mController.getSyncTransactionQueue()); mTaskView = new TaskView(mContext, mTaskViewTaskController); mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener); - mExpandedViewContainer.addView(mTaskView); + + // set a fixed width so it is not recalculated as part of a rotation. the width will be + // updated manually after the rotation. + FrameLayout.LayoutParams lp = + new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT); + mExpandedViewContainer.addView(mTaskView, lp); bringChildToFront(mTaskView); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 1efd9df3a1d9..baa52a0b5626 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -375,6 +375,13 @@ public class BubblePositioner { } } + /** Returns the width of the task view content. */ + public int getTaskViewContentWidth(boolean onLeft) { + int[] paddings = getExpandedViewContainerPadding(onLeft, /* isOverflow = */ false); + int pointerOffset = showBubblesVertically() ? getPointerSize() : 0; + return mPositionRect.width() - paddings[0] - paddings[2] - pointerOffset; + } + /** Gets the y position of the expanded view if it was top-aligned. */ public float getExpandedViewYTopAligned() { final int top = getAvailableRect().top; 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 8f904c42d247..91a8ce726c42 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 @@ -537,8 +537,8 @@ public class BubbleStackView extends FrameLayout return; } - final boolean clickedBubbleIsCurrentlyExpandedBubble = - clickedBubble.getKey().equals(mExpandedBubble.getKey()); + final boolean clickedBubbleIsCurrentlyExpandedBubble = mExpandedBubble != null + && clickedBubble.getKey().equals(mExpandedBubble.getKey()); if (isExpanded()) { mExpandedAnimationController.onGestureFinished(); @@ -3288,6 +3288,7 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble, mPositioner.showBubblesVertically() ? p.y : p.x)); mExpandedViewContainer.setTranslationX(0f); + mExpandedBubble.getExpandedView().updateTaskViewContentWidth(); mExpandedBubble.getExpandedView().updateView( mExpandedViewContainer.getLocationOnScreen()); updatePointerPosition(false /* forIme */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 271a3b26305d..63afd3e7a2ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -627,7 +627,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { && mRecentsTask.equals(change.getContainer()); hasTaskChange = hasTaskChange || isRootTask; final boolean isLeafTask = leafTaskFilter.test(change); - if (TransitionUtil.isOpeningType(change.getMode())) { + if (TransitionUtil.isOpeningType(change.getMode()) + || TransitionUtil.isOrderOnly(change)) { if (isRecentsTask) { recentsOpening = change; } else if (isRootTask || isLeafTask) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index ef8393c3b5b1..35a1fa0a92f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -151,7 +151,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) { - runOnViewThread(() -> setResizeBackgroundColor(t, bgColor)); + if (mHandler.getLooper().isCurrentThread()) { + // We can only use the transaction if it can updated synchronously, otherwise the tx + // will be applied immediately after but also used/updated on the view thread which + // will lead to a race and/or crash + runOnViewThread(() -> setResizeBackgroundColor(t, bgColor)); + } else { + runOnViewThread(() -> setResizeBackgroundColor(bgColor)); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 8cbcde320795..53ec20192f2b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -49,7 +49,6 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; -import com.android.internal.view.BaseIWindow; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; @@ -70,7 +69,9 @@ class DragResizeInputListener implements AutoCloseable { private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; private final int mDisplayId; - private final BaseIWindow mFakeWindow; + + private final IBinder mClientToken; + private final IBinder mFocusGrantToken; private final SurfaceControl mDecorationSurface; private final InputChannel mInputChannel; @@ -78,7 +79,7 @@ class DragResizeInputListener implements AutoCloseable { private final DragPositioningCallback mCallback; private final SurfaceControl mInputSinkSurface; - private final BaseIWindow mFakeSinkWindow; + private final IBinder mSinkClientToken; private final InputChannel mSinkInputChannel; private final DisplayController mDisplayController; @@ -116,17 +117,14 @@ class DragResizeInputListener implements AutoCloseable { mTaskCornerRadius = taskCornerRadius; mDecorationSurface = decorationSurface; mDisplayController = displayController; - // Use a fake window as the backing surface is a container layer, and we don't want to - // create a buffer layer for it, so we can't use ViewRootImpl. - mFakeWindow = new BaseIWindow(); - mFakeWindow.setSession(mWindowSession); + mClientToken = new Binder(); mFocusGrantToken = new Binder(); mInputChannel = new InputChannel(); try { mWindowSession.grantInputChannel( mDisplayId, mDecorationSurface, - mFakeWindow.asBinder(), + mClientToken, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, @@ -155,13 +153,13 @@ class DragResizeInputListener implements AutoCloseable { .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) .show(mInputSinkSurface) .apply(); - mFakeSinkWindow = new BaseIWindow(); + mSinkClientToken = new Binder(); mSinkInputChannel = new InputChannel(); try { mWindowSession.grantInputChannel( mDisplayId, mInputSinkSurface, - mFakeSinkWindow.asBinder(), + mSinkClientToken, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */, @@ -324,14 +322,14 @@ class DragResizeInputListener implements AutoCloseable { mInputEventReceiver.dispose(); mInputChannel.dispose(); try { - mWindowSession.remove(mFakeWindow.asBinder()); + mWindowSession.remove(mClientToken); } catch (RemoteException e) { e.rethrowFromSystemServer(); } mSinkInputChannel.dispose(); try { - mWindowSession.remove(mFakeSinkWindow.asBinder()); + mWindowSession.remove(mSinkClientToken); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp index 386983ce6aae..b9b56c2ae950 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp @@ -132,5 +132,6 @@ android_test { csuite_test { name: "csuite-1p3p-pip-flickers", + test_plan_include: "csuitePlan.xml", test_config_template: "csuiteDefaultTemplate.xml", } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml index 6429b00a2a58..f5a8655b81f0 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml @@ -45,12 +45,15 @@ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> + <option name="run-command" value="settings put global package_verifier_user_consent -1"/> <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard"/> <option name="teardown-command" value="settings delete system show_touches"/> <option name="teardown-command" value="settings delete system pointer_location"/> <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> + <option name="teardown-command" + value="settings put global package_verifier_user_consent 1"/> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuitePlan.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuitePlan.xml new file mode 100644 index 000000000000..a2fc6b45c2ad --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuitePlan.xml @@ -0,0 +1,3 @@ +<configuration description="Flicker tests C-Suite Crawler Test Plan"> + <target_preparer class="com.android.csuite.core.AppCrawlTesterHostPreparer"/> +</configuration>
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java index 45dd38d10ef5..b91d6f90ed8e 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java @@ -13,18 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.filters + +package com.android.wm.shell; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + /** - * An [OutputFilter] that keeps all classes by default. (but none of its members) - * - * We're not currently using it, but using it *might* make certain things easier. For example, with - * this, all classes would at least be loadable. + * Basic test handler that immediately executes anything that is posted on it. */ -class KeepAllClassesFilter(fallback: OutputFilter) : DelegatingFilter(fallback) { - override fun getPolicyForClass(className: String): FilterPolicyWithReason { - // If the default visibility wouldn't keep it, change it to "keep". - val f = super.getPolicyForClass(className) - return f.promoteToKeep("keep-all-classes") +public class TestHandler extends Handler { + public TestHandler(Looper looper) { + super(looper); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + dispatchMessage(msg); + return true; } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index 4afb29ecd98c..d7c46104b6b1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -40,6 +40,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.Context; +import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; @@ -58,6 +59,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestHandler; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable; @@ -92,8 +94,7 @@ public class TaskViewTest extends ShellTestCase { Transitions mTransitions; @Mock Looper mViewLooper; - @Mock - Handler mViewHandler; + TestHandler mViewHandler; SurfaceSession mSession; SurfaceControl mLeash; @@ -112,7 +113,7 @@ public class TaskViewTest extends ShellTestCase { mContext = getContext(); doReturn(true).when(mViewLooper).isCurrentThread(); - doReturn(mViewLooper).when(mViewHandler).getLooper(); + mViewHandler = spy(new TestHandler(mViewLooper)); mTaskInfo = new ActivityManager.RunningTaskInfo(); mTaskInfo.token = mToken; @@ -668,4 +669,24 @@ public class TaskViewTest extends ShellTestCase { mTaskViewTaskController.onTaskInfoChanged(mTaskInfo); verify(mViewHandler).post(any()); } + + @Test + public void testSetResizeBgOnSameUiThread_expectUsesTransaction() { + SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class); + mTaskView = spy(mTaskView); + mTaskView.setResizeBgColor(tx, Color.BLUE); + verify(mViewHandler, never()).post(any()); + verify(mTaskView, never()).setResizeBackgroundColor(eq(Color.BLUE)); + verify(mTaskView).setResizeBackgroundColor(eq(tx), eq(Color.BLUE)); + } + + @Test + public void testSetResizeBgOnDifferentUiThread_expectDoesNotUseTransaction() { + doReturn(false).when(mViewLooper).isCurrentThread(); + SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class); + mTaskView = spy(mTaskView); + mTaskView.setResizeBgColor(tx, Color.BLUE); + verify(mViewHandler).post(any()); + verify(mTaskView).setResizeBackgroundColor(eq(Color.BLUE)); + } } diff --git a/location/api/system-current.txt b/location/api/system-current.txt index a1d6ab5798e1..b1cf96d41497 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -113,13 +113,13 @@ package android.location { } public final class GnssMeasurementRequest implements android.os.Parcelable { - method @NonNull public android.os.WorkSource getWorkSource(); + method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull public android.os.WorkSource getWorkSource(); method public boolean isCorrelationVectorOutputsEnabled(); } public static final class GnssMeasurementRequest.Builder { method @NonNull public android.location.GnssMeasurementRequest.Builder setCorrelationVectorOutputsEnabled(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource); + method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource); } public final class GnssReflectingPlane implements android.os.Parcelable { diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java index 65af3928bd02..2f0835ab8af5 100644 --- a/location/java/android/location/GnssMeasurementRequest.java +++ b/location/java/android/location/GnssMeasurementRequest.java @@ -17,11 +17,13 @@ package android.location; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.location.flags.Flags; import android.os.Parcel; import android.os.Parcelable; import android.os.WorkSource; @@ -121,6 +123,7 @@ public final class GnssMeasurementRequest implements Parcelable { * * @hide */ + @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @SystemApi public @NonNull WorkSource getWorkSource() { return mWorkSource; @@ -298,6 +301,7 @@ public final class GnssMeasurementRequest implements Parcelable { * * @hide */ + @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @SystemApi @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS) public @NonNull Builder setWorkSource(@Nullable WorkSource workSource) { diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig index c471a2749617..b6055e818f8c 100644 --- a/location/java/android/location/flags/gnss.aconfig +++ b/location/java/android/location/flags/gnss.aconfig @@ -5,4 +5,18 @@ flag { namespace: "location" description: "Flag for GNSS API for NavIC L1" bug: "302199306" -}
\ No newline at end of file +} + +flag { + name: "gnss_call_stop_before_set_position_mode" + namespace: "location" + description: "Flag for calling stop() before setPositionMode()" + bug: "306874828" +} + +flag { + name: "gnss_api_measurement_request_work_source" + namespace: "location" + description: "Flag for GnssMeasurementRequest WorkSource API" + bug: "295235160" +} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index d14775fa9ad9..5c8758a6aa73 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -739,7 +739,7 @@ interface IAudioService { oneway void stopLoudnessCodecUpdates(int piid); - oneway void addLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); + oneway void addLoudnessCodecInfo(int piid, int mediaCodecHash, in LoudnessCodecInfo codecInfo); oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java index 92f337244daf..de9d87c0b28c 100644 --- a/media/java/android/media/LoudnessCodecConfigurator.java +++ b/media/java/android/media/LoudnessCodecConfigurator.java @@ -30,10 +30,10 @@ import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; @@ -47,9 +47,6 @@ import java.util.concurrent.atomic.AtomicBoolean; * parameter updates are defined by the CTA-2075 standard. * <p>A new object should be instantiated for each {@link AudioTrack} with the help * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}. - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public class LoudnessCodecConfigurator { @@ -57,9 +54,6 @@ public class LoudnessCodecConfigurator { /** * Listener used for receiving asynchronous loudness metadata updates. - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public interface OnLoudnessCodecUpdateListener { @@ -76,9 +70,6 @@ public class LoudnessCodecConfigurator { * @return a Bundle which contains the original computed codecValues * aggregated with user edits. The platform will configure the associated * MediaCodecs with the returned Bundle params. - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull @@ -112,9 +103,6 @@ public class LoudnessCodecConfigurator { * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * * @return the {@link LoudnessCodecConfigurator} instance - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public static @NonNull LoudnessCodecConfigurator create() { @@ -133,9 +121,6 @@ public class LoudnessCodecConfigurator { * @param listener used for receiving updates * * @return the {@link LoudnessCodecConfigurator} instance - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public static @NonNull LoudnessCodecConfigurator create( @@ -200,12 +185,9 @@ public class LoudnessCodecConfigurator { * method will have the effect of clearing the existing set * {@link AudioTrack} and will stop receiving asynchronous * loudness updates - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setAudioTrack(AudioTrack audioTrack) { + public void setAudioTrack(@Nullable AudioTrack audioTrack) { List<LoudnessCodecInfo> codecInfos; int piid = PLAYER_PIID_INVALID; int oldPiid = PLAYER_PIID_INVALID; @@ -250,10 +232,11 @@ public class LoudnessCodecConfigurator { * previously added. * * @param mediaCodec the codec to start receiving asynchronous loudness - * updates - * - * TODO: remove hide once API is final - * @hide + * updates. The codec has to be in a configured or started + * state in order to add it for loudness updates. + * @throws IllegalArgumentException if the {@code mediaCodec} was not configured, + * does not contain loudness metadata or if it + * was already added before */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public void addMediaCodec(@NonNull MediaCodec mediaCodec) { @@ -262,31 +245,32 @@ public class LoudnessCodecConfigurator { int piid = PLAYER_PIID_INVALID; final LoudnessCodecInfo mcInfo = getCodecInfo(mc); - if (mcInfo != null) { - synchronized (mConfiguratorLock) { - final AtomicBoolean containsCodec = new AtomicBoolean(false); - Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> { - containsCodec.set(!codecSet.add(mc)); - return codecSet; - }); - if (newSet == null) { - newSet = new HashSet<>(); - newSet.add(mc); - mMediaCodecs.put(mcInfo, newSet); - } - if (containsCodec.get()) { - Log.v(TAG, "Loudness configurator already added media codec " + mediaCodec); - return; - } - if (mAudioTrack != null) { - piid = mAudioTrack.getPlayerIId(); - } + if (mcInfo == null) { + throw new IllegalArgumentException("Could not extract codec loudness information"); + } + synchronized (mConfiguratorLock) { + final AtomicBoolean containsCodec = new AtomicBoolean(false); + Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> { + containsCodec.set(!codecSet.add(mc)); + return codecSet; + }); + if (newSet == null) { + newSet = new HashSet<>(); + newSet.add(mc); + mMediaCodecs.put(mcInfo, newSet); } - - if (piid != PLAYER_PIID_INVALID) { - mLcDispatcher.addLoudnessCodecInfo(piid, mcInfo); + if (containsCodec.get()) { + throw new IllegalArgumentException( + "Loudness configurator already added " + mediaCodec); + } + if (mAudioTrack != null) { + piid = mAudioTrack.getPlayerIId(); } } + + if (piid != PLAYER_PIID_INVALID) { + mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo); + } } /** @@ -297,37 +281,44 @@ public class LoudnessCodecConfigurator { * <p>No elements will be removed if the passed mediaCodec was not added before. * * @param mediaCodec the element to remove for receiving asynchronous updates - * - * TODO: remove hide once API is final - * @hide + * @throws IllegalArgumentException if the {@code mediaCodec} was not configured, + * does not contain loudness metadata or if it + * was not added before */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public void removeMediaCodec(@NonNull MediaCodec mediaCodec) { int piid = PLAYER_PIID_INVALID; LoudnessCodecInfo mcInfo; - AtomicBoolean removed = new AtomicBoolean(false); + AtomicBoolean removedMc = new AtomicBoolean(false); + AtomicBoolean removeInfo = new AtomicBoolean(false); mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec, "MediaCodec for removeMediaCodec cannot be null")); - if (mcInfo != null) { - synchronized (mConfiguratorLock) { - if (mAudioTrack != null) { - piid = mAudioTrack.getPlayerIId(); + if (mcInfo == null) { + throw new IllegalArgumentException("Could not extract codec loudness information"); + } + synchronized (mConfiguratorLock) { + if (mAudioTrack != null) { + piid = mAudioTrack.getPlayerIId(); + } + mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> { + removedMc.set(mcs.remove(mediaCodec)); + if (mcs.isEmpty()) { + // remove the entry + removeInfo.set(true); + return null; } - mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> { - removed.set(mcs.remove(mediaCodec)); - if (mcs.isEmpty()) { - // remove the entry - return null; - } - return mcs; - }); + return mcs; + }); + if (!removedMc.get()) { + throw new IllegalArgumentException( + "Loudness configurator does not contain " + mediaCodec); } + } - if (piid != PLAYER_PIID_INVALID && removed.get()) { - mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo); - } + if (piid != PLAYER_PIID_INVALID && removeInfo.get()) { + mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo); } } @@ -342,9 +333,6 @@ public class LoudnessCodecConfigurator { * * @return the {@link Bundle} containing the current loudness parameters. Caller is * responsible to update the {@link MediaCodec} - * - * TODO: remove hide once API is final - * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull @@ -375,9 +363,9 @@ public class LoudnessCodecConfigurator { } /** @hide */ - /*package*/ List<MediaCodec> getRegisteredMediaCodecList() { + /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() { synchronized (mConfiguratorLock) { - return mMediaCodecs.values().stream().flatMap(Collection::stream).toList(); + return mMediaCodecs; } } @@ -397,40 +385,43 @@ public class LoudnessCodecConfigurator { return null; } - final MediaFormat inputFormat = mediaCodec.getInputFormat(); - final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME); - if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) { - // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of - // these two keys - int aacProfile = -1; - int profile = -1; - try { - aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE); - } catch (NullPointerException e) { - // does not contain KEY_AAC_PROFILE. do nothing - } - try { - profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE); - } catch (NullPointerException e) { - // does not contain KEY_PROFILE. do nothing - } - if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE - || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) { - lci.metadataType = CODEC_METADATA_TYPE_MPEG_D; + try { + final MediaFormat inputFormat = mediaCodec.getInputFormat(); + final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME); + if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) { + // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize + // one of these two keys + int aacProfile = -1; + int profile = -1; + try { + aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE); + } catch (NullPointerException e) { + // does not contain KEY_AAC_PROFILE. do nothing + } + try { + profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE); + } catch (NullPointerException e) { + // does not contain KEY_PROFILE. do nothing + } + if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE + || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) { + lci.metadataType = CODEC_METADATA_TYPE_MPEG_D; + } else { + lci.metadataType = CODEC_METADATA_TYPE_MPEG_4; + } } else { - lci.metadataType = CODEC_METADATA_TYPE_MPEG_4; + Log.w(TAG, "MediaCodec mime type not supported for loudness annotation"); + return null; } - } else { - Log.w(TAG, "MediaCodec mime type not supported for loudness annotation"); + + final MediaFormat outputFormat = mediaCodec.getOutputFormat(); + lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) + < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + } catch (IllegalStateException e) { + Log.e(TAG, "MediaCodec is not configured", e); return null; } - final MediaFormat outputFormat = mediaCodec.getOutputFormat(); - lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) - < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); - - lci.mediaCodecHashCode = mediaCodec.hashCode(); - return lci; } } diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java index be881b11e545..b546a81b0498 100644 --- a/media/java/android/media/LoudnessCodecDispatcher.java +++ b/media/java/android/media/LoudnessCodecDispatcher.java @@ -27,12 +27,16 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.util.Log; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; /** @@ -52,6 +56,9 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener> mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>(); + private final Object mLock = new Object(); + + @GuardedBy("mLock") private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> mConfiguratorListener = new HashMap<>(); @@ -66,38 +73,56 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { @Override public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) { - mLoudnessListenerMgr.callListeners(listener -> + if (DEBUG) { + Log.d(TAG, "dispatchLoudnessCodecParameterChange for piid " + piid + + " persistable bundle: " + params); + } + mLoudnessListenerMgr.callListeners(listener -> { + synchronized (mLock) { mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> { // send the appropriate bundle for the user to update if (lcConfig.getAssignedTrackPiid() == piid) { - final List<MediaCodec> mediaCodecs = - lcConfig.getRegisteredMediaCodecList(); - for (MediaCodec mediaCodec : mediaCodecs) { - final String infoKey = Integer.toString(mediaCodec.hashCode()); + final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap = + lcConfig.getRegisteredMediaCodecs(); + for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) { + final String infoKey = Integer.toString(codecInfo.hashCode()); + Bundle bundle = null; if (params.containsKey(infoKey)) { - Bundle bundle = new Bundle( - params.getPersistableBundle(infoKey)); - if (DEBUG) { - Log.d(TAG, - "Received for piid " + piid + " bundle: " + bundle); + bundle = new Bundle(params.getPersistableBundle(infoKey)); + } + + final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo); + for (MediaCodec mediaCodec : mediaCodecs) { + final String mediaCodecKey = Integer.toString( + mediaCodec.hashCode()); + if (bundle == null && !params.containsKey(mediaCodecKey)) { + continue; + } + boolean canBreak = false; + if (bundle == null) { + // key was set by media codec hash to update single codec + bundle = new Bundle( + params.getPersistableBundle(mediaCodecKey)); + canBreak = true; } bundle = LoudnessCodecUpdatesDispatcherStub.filterLoudnessParams( - l.onLoudnessCodecUpdate(mediaCodec, bundle)); - if (DEBUG) { - Log.d(TAG, "User changed for piid " + piid - + " to filtered bundle: " + bundle); - } + l.onLoudnessCodecUpdate(mediaCodec, + bundle)); if (!bundle.isDefinitelyEmpty()) { mediaCodec.setParameters(bundle); } + if (canBreak) { + break; + } } } } - return lcConfig; - })); + }); + } + }); } private static Bundle filterLoudnessParams(Bundle bundle) { @@ -130,21 +155,33 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { mLoudnessListenerMgr.addListener( executor, listener, "addLoudnessCodecListener", () -> dispatcher); - mConfiguratorListener.put(listener, configurator); + synchronized (mLock) { + mConfiguratorListener.put(listener, configurator); + } } void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) { Objects.requireNonNull(configurator); - for (Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e : - mConfiguratorListener.entrySet()) { - if (e.getValue() == configurator) { - final OnLoudnessCodecUpdateListener listener = e.getKey(); - mConfiguratorListener.remove(listener); - mLoudnessListenerMgr.removeListener(listener, "removeLoudnessCodecListener"); - break; + OnLoudnessCodecUpdateListener listenerToRemove = null; + synchronized (mLock) { + Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>> iterator = + mConfiguratorListener.entrySet().iterator(); + while (iterator.hasNext()) { + Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e = + iterator.next(); + if (e.getValue() == configurator) { + final OnLoudnessCodecUpdateListener listener = e.getKey(); + iterator.remove(); + listenerToRemove = listener; + break; + } } } + if (listenerToRemove != null) { + mLoudnessListenerMgr.removeListener(listenerToRemove, + "removeLoudnessCodecListener"); + } } } @@ -202,9 +239,10 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { } /** @hide */ - public void addLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) { + public void addLoudnessCodecInfo(int piid, int mediaCodecHash, + @NonNull LoudnessCodecInfo mcInfo) { try { - mAudioService.addLoudnessCodecInfo(piid, mcInfo); + mAudioService.addLoudnessCodecInfo(piid, mediaCodecHash, mcInfo); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/LoudnessCodecInfo.aidl b/media/java/android/media/LoudnessCodecInfo.aidl index fd695179057d..0ac5646a3047 100644 --- a/media/java/android/media/LoudnessCodecInfo.aidl +++ b/media/java/android/media/LoudnessCodecInfo.aidl @@ -23,7 +23,7 @@ package android.media; * * {@hide} */ -@JavaDerive(equals = true) +@JavaDerive(equals = true, toString = true) parcelable LoudnessCodecInfo { /** Supported codec metadata types for loudness updates. */ @Backing(type="int") @@ -37,7 +37,6 @@ parcelable LoudnessCodecInfo { CODEC_METADATA_TYPE_DTS_UHD = 6 } - int mediaCodecHashCode; CodecMetadataType metadataType; boolean isDownmixing; }
\ No newline at end of file diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java index aa5a290346b0..2b52c4b107b6 100644 --- a/media/java/android/media/tv/ad/TvAdManager.java +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -16,6 +16,10 @@ package android.media.tv.ad; +import android.annotation.FlaggedApi; +import android.annotation.SystemService; +import android.content.Context; +import android.media.tv.flags.Flags; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -23,14 +27,17 @@ import android.util.Log; /** * Central system API to the overall client-side TV AD architecture, which arbitrates interaction * between applications and AD services. - * @hide */ +@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) +@SystemService(Context.TV_AD_SERVICE) public class TvAdManager { + // TODO: implement more methods and unhide APIs. private static final String TAG = "TvAdManager"; private final ITvAdManager mService; private final int mUserId; + /** @hide */ public TvAdManager(ITvAdManager service, int userId) { mService = service; mUserId = userId; @@ -38,6 +45,7 @@ public class TvAdManager { /** * The Session provides the per-session functionality of AD service. + * @hide */ public static final class Session { private final IBinder mToken; diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig index a73d1ff72a17..018eaf6dc32f 100644 --- a/media/java/android/media/tv/flags/media_tv.aconfig +++ b/media/java/android/media/tv/flags/media_tv.aconfig @@ -5,4 +5,11 @@ flag { namespace: "media_tv" description: "Constants for standardizing broadcast visibility types." bug: "222402395" +} + +flag { + name: "enable_ad_service_fw" + namespace: "media_tv" + description: "Enable the TV client-side AD framework." + bug: "303506816" }
\ No newline at end of file diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java index 65a9799431e7..c9e36b7f10bd 100644 --- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java +++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java @@ -208,7 +208,7 @@ public class LoudnessCodecConfiguratorTest { verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); mLcc.addMediaCodec(createAndConfigureMediaCodec()); - verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any()); } @Test diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp index fe26dc3d7feb..991fe41bb7f3 100644 --- a/packages/CredentialManager/Android.bp +++ b/packages/CredentialManager/Android.bp @@ -16,10 +16,12 @@ android_app { dex_preopt: { profile_guided: true, + //TODO: b/312357299 - Update baseline profile profile: "profile.txt.prof", }, static_libs: [ + "CredentialManagerShared", "PlatformComposeCore", "androidx.activity_activity-compose", "androidx.appcompat_appcompat", diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt index 42f1207c69cb..325d3f819f14 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt @@ -16,6 +16,7 @@ package com.android.credentialmanager +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.credentials.ui.RequestInfo @@ -27,10 +28,10 @@ import com.android.credentialmanager.mapper.toGet import com.android.credentialmanager.model.Request fun Intent.parse( - packageManager: PackageManager, + context: Context, ): Request { - return parseCancelUiRequest(packageManager) - ?: parseRequestInfo() + return parseCancelUiRequest(context.packageManager) + ?: parseRequestInfo(context) } fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? = @@ -51,11 +52,11 @@ fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? = } } -fun Intent.parseRequestInfo(): Request = +fun Intent.parseRequestInfo(context: Context): Request = requestInfo.let{ info -> when (info?.type) { RequestInfo.TYPE_CREATE -> Request.Create(info.token) - RequestInfo.TYPE_GET -> toGet() + RequestInfo.TYPE_GET -> toGet(context) else -> { throw IllegalStateException("Unrecognized request type: ${info?.type}") } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt index 83183b5f58eb..3ef65b052560 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt @@ -16,8 +16,8 @@ package com.android.credentialmanager.client.impl +import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.credentials.ui.BaseDialogResult import android.credentials.ui.UserSelectionDialogResult import android.os.Bundle @@ -26,12 +26,13 @@ import com.android.credentialmanager.TAG import com.android.credentialmanager.model.Request import com.android.credentialmanager.parse import com.android.credentialmanager.client.CredentialManagerClient +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject class CredentialManagerClientImpl @Inject constructor( - private val packageManager: PackageManager, + @ApplicationContext private val context: Context, ) : CredentialManagerClient { private val _requests = MutableStateFlow<Request?>(null) @@ -40,7 +41,7 @@ class CredentialManagerClientImpl @Inject constructor( override fun updateRequest(intent: Intent) { val request = intent.parse( - packageManager = packageManager, + context = context, ) Log.d(TAG, "Request parsed: $request, client instance: $this") if (request is Request.Cancel || request is Request.Close) { diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt new file mode 100644 index 000000000000..f063074b39b4 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2023 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.credentialmanager.ktx + +import android.app.slice.Slice +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.credentials.Credential +import android.credentials.flags.Flags +import android.credentials.ui.AuthenticationEntry +import android.credentials.ui.Entry +import android.credentials.ui.GetCredentialProviderData +import android.graphics.drawable.Drawable +import android.text.TextUtils +import android.util.Log +import androidx.activity.result.IntentSenderRequest +import androidx.credentials.PublicKeyCredential +import androidx.credentials.provider.Action +import androidx.credentials.provider.AuthenticationAction +import androidx.credentials.provider.CredentialEntry +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry +import androidx.credentials.provider.RemoteEntry +import com.android.credentialmanager.IS_AUTO_SELECTED_KEY +import com.android.credentialmanager.model.get.ActionEntryInfo +import com.android.credentialmanager.model.get.AuthenticationEntryInfo +import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.get.ProviderInfo +import com.android.credentialmanager.model.get.RemoteEntryInfo +import com.android.credentialmanager.TAG + +fun CredentialEntryInfo.getIntentSenderRequest( + isAutoSelected: Boolean = false +): IntentSenderRequest? { + val entryIntent = fillInIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected) + + return pendingIntent?.let{ + IntentSenderRequest + .Builder(pendingIntent = it) + .setFillInIntent(entryIntent) + .build() + } +} + +// Returns the list (potentially empty) of enabled provider. +fun List<GetCredentialProviderData>.toProviderList( + context: Context, +): List<ProviderInfo> { + val providerList: MutableList<ProviderInfo> = mutableListOf() + this.forEach { + val providerLabelAndIcon = getServiceLabelAndIcon( + context.packageManager, + it.providerFlattenedComponentName + ) ?: return@forEach + val (providerLabel, providerIcon) = providerLabelAndIcon + providerList.add( + ProviderInfo( + id = it.providerFlattenedComponentName, + icon = providerIcon, + displayName = providerLabel, + credentialEntryList = getCredentialOptionInfoList( + providerId = it.providerFlattenedComponentName, + providerLabel = providerLabel, + credentialEntries = it.credentialEntries, + context = context + ), + authenticationEntryList = getAuthenticationEntryList( + it.providerFlattenedComponentName, + providerLabel, + providerIcon, + it.authenticationEntries), + remoteEntry = getRemoteEntry( + it.providerFlattenedComponentName, + it.remoteEntry + ), + actionEntryList = getActionEntryList( + it.providerFlattenedComponentName, it.actionChips, providerIcon + ), + ) + ) + } + return providerList +} + +/** + * Note: caller required handle empty list due to parsing error. + */ +private fun getCredentialOptionInfoList( + providerId: String, + providerLabel: String, + credentialEntries: List<Entry>, + context: Context, +): List<CredentialEntryInfo> { + val result: MutableList<CredentialEntryInfo> = mutableListOf() + credentialEntries.forEach { + val credentialEntry = it.slice.credentialEntry + when (credentialEntry) { + is PasswordCredentialEntry -> { + result.add( + CredentialEntryInfo( + providerId = providerId, + providerDisplayName = providerLabel, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = credentialEntry.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + credentialType = CredentialType.PASSWORD, + credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), + userName = credentialEntry.username.toString(), + displayName = credentialEntry.displayName?.toString(), + icon = credentialEntry.icon.loadDrawable(context), + shouldTintIcon = credentialEntry.isDefaultIcon, + lastUsedTimeMillis = credentialEntry.lastUsedTime, + isAutoSelectable = credentialEntry.isAutoSelectAllowed && + credentialEntry.autoSelectAllowedFromOption, + ) + ) + } + is PublicKeyCredentialEntry -> { + result.add( + CredentialEntryInfo( + providerId = providerId, + providerDisplayName = providerLabel, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = credentialEntry.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + credentialType = CredentialType.PASSKEY, + credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), + userName = credentialEntry.username.toString(), + displayName = credentialEntry.displayName?.toString(), + icon = credentialEntry.icon.loadDrawable(context), + shouldTintIcon = credentialEntry.isDefaultIcon, + lastUsedTimeMillis = credentialEntry.lastUsedTime, + isAutoSelectable = credentialEntry.isAutoSelectAllowed && + credentialEntry.autoSelectAllowedFromOption, + ) + ) + } + is CustomCredentialEntry -> { + result.add( + CredentialEntryInfo( + providerId = providerId, + providerDisplayName = providerLabel, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = credentialEntry.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + credentialType = CredentialType.UNKNOWN, + credentialTypeDisplayName = + credentialEntry.typeDisplayName?.toString().orEmpty(), + userName = credentialEntry.title.toString(), + displayName = credentialEntry.subtitle?.toString(), + icon = credentialEntry.icon.loadDrawable(context), + shouldTintIcon = credentialEntry.isDefaultIcon, + lastUsedTimeMillis = credentialEntry.lastUsedTime, + isAutoSelectable = credentialEntry.isAutoSelectAllowed && + credentialEntry.autoSelectAllowedFromOption, + ) + ) + } + else -> Log.d( + TAG, + "Encountered unrecognized credential entry ${it.slice.spec?.type}" + ) + } + } + return result +} +val Slice.credentialEntry: CredentialEntry? + get() = + try { + when (spec?.type) { + Credential.TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntry.fromSlice(this)!! + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> + PublicKeyCredentialEntry.fromSlice(this)!! + + else -> CustomCredentialEntry.fromSlice(this)!! + } + } catch (e: Exception) { + // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed + // password / passkey parsing attempt. + CustomCredentialEntry.fromSlice(this) + } + + +/** + * Note: caller required handle empty list due to parsing error. + */ +private fun getAuthenticationEntryList( + providerId: String, + providerDisplayName: String, + providerIcon: Drawable, + authEntryList: List<AuthenticationEntry>, +): List<AuthenticationEntryInfo> { + val result: MutableList<AuthenticationEntryInfo> = mutableListOf() + authEntryList.forEach { entry -> + val structuredAuthEntry = + AuthenticationAction.fromSlice(entry.slice) ?: return@forEach + + val title: String = + structuredAuthEntry.title.toString().ifEmpty { providerDisplayName } + + result.add( + AuthenticationEntryInfo( + providerId = providerId, + entryKey = entry.key, + entrySubkey = entry.subkey, + pendingIntent = structuredAuthEntry.pendingIntent, + fillInIntent = entry.frameworkExtrasIntent, + title = title, + providerDisplayName = providerDisplayName, + icon = providerIcon, + isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED, + isLastUnlocked = + entry.status == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT + ) + ) + } + return result +} + +private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? { + if (remoteEntry == null) { + return null + } + val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice) + ?: return null + return RemoteEntryInfo( + providerId = providerId, + entryKey = remoteEntry.key, + entrySubkey = remoteEntry.subkey, + pendingIntent = structuredRemoteEntry.pendingIntent, + fillInIntent = remoteEntry.frameworkExtrasIntent, + ) +} + +/** + * Note: caller required handle empty list due to parsing error. + */ +private fun getActionEntryList( + providerId: String, + actionEntries: List<Entry>, + providerIcon: Drawable, +): List<ActionEntryInfo> { + val result: MutableList<ActionEntryInfo> = mutableListOf() + actionEntries.forEach { + val actionEntryUi = Action.fromSlice(it.slice) ?: return@forEach + result.add( + ActionEntryInfo( + providerId = providerId, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = actionEntryUi.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + title = actionEntryUi.title.toString(), + icon = providerIcon, + subTitle = actionEntryUi.subtitle?.toString(), + ) + ) + } + return result +} + + + +private fun getServiceLabelAndIcon( + pm: PackageManager, + providerFlattenedComponentName: String +): Pair<String, Drawable>? { + var providerLabel: String? = null + var providerIcon: Drawable? = null + val component = ComponentName.unflattenFromString(providerFlattenedComponentName) + if (component == null) { + // Test data has only package name not component name. + // For test data usage only. + try { + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + providerFlattenedComponentName, + PackageManager.PackageInfoFlags.of(0) + ) + } + val applicationInfo = checkNotNull(pkgInfo.applicationInfo) + providerLabel = + applicationInfo.loadSafeLabel( + pm, 0f, + TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM + ).toString() + providerIcon = applicationInfo.loadIcon(pm) + } catch (e: Exception) { + Log.e(TAG, "Provider package info not found", e) + } + } else { + try { + val si = pm.getServiceInfo(component, PackageManager.ComponentInfoFlags.of(0)) + providerLabel = si.loadSafeLabel( + pm, 0f, + TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM + ).toString() + providerIcon = si.loadIcon(pm) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Provider service info not found", e) + // Added for mdoc use case where the provider may not need to register a service and + // instead only relies on the registration api. + try { + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + component.packageName, + PackageManager.PackageInfoFlags.of(0) + ) + } + val applicationInfo = checkNotNull(pkgInfo.applicationInfo) + providerLabel = + applicationInfo.loadSafeLabel( + pm, 0f, + TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM + ).toString() + providerIcon = applicationInfo.loadIcon(pm) + } catch (e: Exception) { + Log.e(TAG, "Provider package info not found", e) + } + } + } + return if (providerLabel == null || providerIcon == null) { + Log.d( + TAG, + "Failed to load provider label/icon for provider $providerFlattenedComponentName" + ) + null + } else { + Pair(providerLabel, providerIcon) + } +} + +private fun getPackageInfo( + pm: PackageManager, + packageName: String +): PackageInfo { + val packageManagerFlags = PackageManager.MATCH_INSTANT + + return pm.getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of( + (packageManagerFlags).toLong()) + ) +}
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt deleted file mode 100644 index 34710704facb..000000000000 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 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.0N - * - * 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.credentialmanager.ktx - -import androidx.activity.result.IntentSenderRequest -import com.android.credentialmanager.IS_AUTO_SELECTED_KEY -import com.android.credentialmanager.model.Password - -fun Password.getIntentSenderRequest( - isAutoSelected: Boolean = false -): IntentSenderRequest { - val entryIntent = entry.frameworkExtrasIntent - entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected) - - return IntentSenderRequest.Builder( - pendingIntent = passwordCredentialEntry.pendingIntent - ).setFillInIntent(entryIntent).build() -} diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt index d4bca2add6cb..f1f1f7ca842e 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt @@ -16,48 +16,18 @@ package com.android.credentialmanager.mapper +import android.content.Context import android.content.Intent -import android.credentials.ui.Entry -import androidx.credentials.provider.PasswordCredentialEntry -import com.android.credentialmanager.factory.fromSlice import com.android.credentialmanager.ktx.getCredentialProviderDataList import com.android.credentialmanager.ktx.requestInfo import com.android.credentialmanager.ktx.resultReceiver -import com.android.credentialmanager.model.Password +import com.android.credentialmanager.ktx.toProviderList import com.android.credentialmanager.model.Request -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableMap - -fun Intent.toGet(): Request.Get { - val credentialEntries = mutableListOf<Pair<String, Entry>>() - for (providerData in getCredentialProviderDataList) { - for (credentialEntry in providerData.credentialEntries) { - credentialEntries.add( - Pair(providerData.providerFlattenedComponentName, credentialEntry) - ) - } - } - - val passwordEntries = mutableListOf<Password>() - for ((providerId, entry) in credentialEntries) { - val slice = fromSlice(entry.slice) - if (slice is PasswordCredentialEntry) { - passwordEntries.add( - Password( - providerId = providerId, - entry = entry, - passwordCredentialEntry = slice - ) - ) - } - } +fun Intent.toGet(context: Context): Request.Get { return Request.Get( token = requestInfo?.token, - resultReceiver = this.resultReceiver, - providers = ImmutableMap.copyOf( - getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } - ), - passwordEntries = ImmutableList.copyOf(passwordEntries) + resultReceiver = resultReceiver, + providerInfos = getCredentialProviderDataList.toProviderList(context) ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/CredentialType.kt index cc92f60de048..3f85192d937f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/CredentialType.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.common +package com.android.credentialmanager.model enum class CredentialType { UNKNOWN, PASSKEY, PASSWORD, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/EntryInfo.kt index ee36989b3da0..6d59f11d5643 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/EntryInfo.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.credentialmanager.common +package com.android.credentialmanager.model import android.app.PendingIntent import android.content.Intent -open class BaseEntry ( +open class EntryInfo ( val providerId: String, val entryKey: String, val entrySubkey: String, diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt index 2289ed7320ca..763646233324 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt @@ -16,11 +16,9 @@ package com.android.credentialmanager.model -import android.credentials.ui.ProviderData import android.os.IBinder import android.os.ResultReceiver -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableMap +import com.android.credentialmanager.model.get.ProviderInfo /** * Represents the request made by the CredentialManager API. @@ -51,8 +49,7 @@ sealed class Request private constructor( data class Get( override val token: IBinder?, val resultReceiver: ResultReceiver?, - val providers: ImmutableMap<String, ProviderData>, - val passwordEntries: ImmutableList<Password>, + val providerInfos: List<ProviderInfo>, ) : Request(token) /** * Request to start the create credentials flow. diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/CreateOptionInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/CreateOptionInfo.kt new file mode 100644 index 000000000000..d6189eb15ff3 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/CreateOptionInfo.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.creation + +import android.app.PendingIntent +import android.content.Intent +import android.graphics.drawable.Drawable +import com.android.credentialmanager.model.EntryInfo +import java.time.Instant + +class CreateOptionInfo( + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, + val userProviderDisplayName: String, + val profileIcon: Drawable?, + val passwordCount: Int?, + val passkeyCount: Int?, + val totalCredentialCount: Int?, + val lastUsedTime: Instant, + val footerDescription: String?, + val allowAutoSelect: Boolean, +) : EntryInfo( + providerId, + entryKey, + entrySubkey, + pendingIntent, + fillInIntent, + shouldTerminateUiUponSuccessfulProviderResult = true, +)
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt new file mode 100644 index 000000000000..7ee50d7c4e73 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.creation + +import android.app.PendingIntent +import android.content.Intent +import com.android.credentialmanager.model.EntryInfo + +class RemoteInfo( + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, +) : EntryInfo( + providerId, + entryKey, + entrySubkey, + pendingIntent, + fillInIntent, + shouldTerminateUiUponSuccessfulProviderResult = true, +)
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ActionEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ActionEntryInfo.kt new file mode 100644 index 000000000000..d9eee868c17f --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ActionEntryInfo.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.get + +import android.app.PendingIntent +import android.content.Intent +import android.graphics.drawable.Drawable +import com.android.credentialmanager.model.EntryInfo + +class ActionEntryInfo( + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, + val title: String, + val icon: Drawable, + val subTitle: String?, +) : EntryInfo( + providerId, + entryKey, + entrySubkey, + pendingIntent, + fillInIntent, + shouldTerminateUiUponSuccessfulProviderResult = true, +)
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/AuthenticationEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/AuthenticationEntryInfo.kt new file mode 100644 index 000000000000..01c394fc0d74 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/AuthenticationEntryInfo.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.get + +import android.app.PendingIntent +import android.content.Intent +import android.graphics.drawable.Drawable +import com.android.credentialmanager.model.EntryInfo + +class AuthenticationEntryInfo( + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, + val title: String, + val providerDisplayName: String, + val icon: Drawable, + // The entry had been unlocked and turned out to be empty. Used to determine whether to + // show "Tap to unlock" or "No sign-in info" for this entry. + val isUnlockedAndEmpty: Boolean, + // True if the entry was the last one unlocked. Used to show the no sign-in info snackbar. + val isLastUnlocked: Boolean, +) : EntryInfo( + providerId, + entryKey, entrySubkey, + pendingIntent, + fillInIntent, + shouldTerminateUiUponSuccessfulProviderResult = false, +)
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt new file mode 100644 index 000000000000..9725881e1c97 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.get + +import android.app.PendingIntent +import android.content.Intent +import android.graphics.drawable.Drawable +import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.EntryInfo +import java.time.Instant + +class CredentialEntryInfo( + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, + /** Type of this credential used for sorting. Not localized so must not be directly displayed. */ + val credentialType: CredentialType, + /** Localized type value of this credential used for display purpose. */ + val credentialTypeDisplayName: String, + val providerDisplayName: String, + val userName: String, + val displayName: String?, + val icon: Drawable?, + val shouldTintIcon: Boolean, + val lastUsedTimeMillis: Instant?, + val isAutoSelectable: Boolean, +) : EntryInfo( + providerId, + entryKey, + entrySubkey, + pendingIntent, + fillInIntent, + shouldTerminateUiUponSuccessfulProviderResult = true, +)
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ProviderInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ProviderInfo.kt new file mode 100644 index 000000000000..6da414617752 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ProviderInfo.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.get + +import android.graphics.drawable.Drawable + +data class ProviderInfo( + /** + * Unique id (component name) of this provider. + * Not for display purpose - [displayName] should be used for ui rendering. + */ + val id: String, + val icon: Drawable, + val displayName: String, + val credentialEntryList: List<CredentialEntryInfo>, + val authenticationEntryList: List<AuthenticationEntryInfo>, + val remoteEntry: RemoteEntryInfo?, + val actionEntryList: List<ActionEntryInfo>, +)
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt new file mode 100644 index 000000000000..a68bf7413b25 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 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.credentialmanager.model.get + +import android.app.PendingIntent +import android.content.Intent +import com.android.credentialmanager.model.EntryInfo + +class RemoteEntryInfo( + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, +) : EntryInfo( + providerId, + entryKey, + entrySubkey, + pendingIntent, + fillInIntent, + shouldTerminateUiUponSuccessfulProviderResult = true, +)
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index bce86c477e77..6c5a984f20f7 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -28,7 +28,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import com.android.credentialmanager.common.BaseEntry +import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.common.Constants import com.android.credentialmanager.common.DialogState import com.android.credentialmanager.common.ProviderActivityResult @@ -47,7 +47,7 @@ import com.android.internal.logging.UiEventLogger.UiEventEnum data class UiState( val createCredentialUiState: CreateCredentialUiState?, val getCredentialUiState: GetCredentialUiState?, - val selectedEntry: BaseEntry? = null, + val selectedEntry: EntryInfo? = null, val providerActivityState: ProviderActivityState = ProviderActivityState.NOT_APPLICABLE, val dialogState: DialogState = DialogState.ACTIVE, // True if the UI has one and only one auto selectable entry. Its provider activity will be @@ -115,12 +115,13 @@ class CredentialSelectorViewModel( launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult> ) { val entry = uiState.selectedEntry - if (entry != null && entry.pendingIntent != null) { + val pendingIntent = entry?.pendingIntent + if (pendingIntent != null) { Log.d(Constants.LOG_TAG, "Launching provider activity") uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING) val entryIntent = entry.fillInIntent entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow) - val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent) + val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent) .setFillInIntent(entryIntent).build() try { launcher.launch(intentSenderRequest) @@ -201,7 +202,7 @@ class CredentialSelectorViewModel( /**************************************************************************/ /***** Get Flow Callbacks *****/ /**************************************************************************/ - fun getFlowOnEntrySelected(entry: BaseEntry) { + fun getFlowOnEntrySelected(entry: EntryInfo) { Log.d(Constants.LOG_TAG, "credential selected: {provider=${entry.providerId}" + ", key=${entry.entryKey}, subkey=${entry.entrySubkey}}") uiState = if (entry.pendingIntent != null) { @@ -363,7 +364,7 @@ class CredentialSelectorViewModel( ) } - fun createFlowOnEntrySelected(selectedEntry: BaseEntry) { + fun createFlowOnEntrySelected(selectedEntry: EntryInfo) { val providerId = selectedEntry.providerId val entryKey = selectedEntry.entryKey val entrySubkey = selectedEntry.entrySubkey diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index f0fa6c5c4dd2..fc3970de2cee 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -16,13 +16,10 @@ package com.android.credentialmanager -import android.app.slice.Slice import android.content.ComponentName import android.content.Context import android.content.pm.PackageInfo import android.content.pm.PackageManager -import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL -import android.credentials.ui.AuthenticationEntry import android.credentials.ui.CreateCredentialProviderData import android.credentials.ui.DisabledProviderData import android.credentials.ui.Entry @@ -32,36 +29,26 @@ import android.graphics.drawable.Drawable import android.text.TextUtils import android.util.Log import com.android.credentialmanager.common.Constants -import com.android.credentialmanager.common.CredentialType +import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.CreateCredentialUiState -import com.android.credentialmanager.createflow.CreateOptionInfo +import com.android.credentialmanager.model.creation.CreateOptionInfo import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.createflow.DisabledProviderInfo import com.android.credentialmanager.createflow.EnabledProviderInfo -import com.android.credentialmanager.createflow.RemoteInfo +import com.android.credentialmanager.model.creation.RemoteInfo import com.android.credentialmanager.createflow.RequestDisplayInfo -import com.android.credentialmanager.getflow.ActionEntryInfo -import com.android.credentialmanager.getflow.AuthenticationEntryInfo -import com.android.credentialmanager.getflow.CredentialEntryInfo -import com.android.credentialmanager.getflow.ProviderInfo -import com.android.credentialmanager.getflow.RemoteEntryInfo -import com.android.credentialmanager.getflow.TopBrandingContent +import com.android.credentialmanager.model.get.ProviderInfo +import com.android.credentialmanager.ktx.toProviderList import androidx.credentials.CreateCredentialRequest import androidx.credentials.CreateCustomCredentialRequest import androidx.credentials.CreatePasswordRequest import androidx.credentials.CreatePublicKeyCredentialRequest -import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL -import androidx.credentials.provider.Action -import androidx.credentials.provider.AuthenticationAction import androidx.credentials.provider.CreateEntry -import androidx.credentials.provider.CredentialEntry -import androidx.credentials.provider.CustomCredentialEntry -import androidx.credentials.provider.PasswordCredentialEntry -import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject import android.credentials.flags.Flags +import com.android.credentialmanager.getflow.TopBrandingContent import java.time.Instant @@ -179,43 +166,7 @@ class GetFlowUtils { fun toProviderList( providerDataList: List<GetCredentialProviderData>, context: Context, - ): List<ProviderInfo> { - val providerList: MutableList<ProviderInfo> = mutableListOf() - providerDataList.forEach { - val providerLabelAndIcon = getServiceLabelAndIcon( - context.packageManager, - it.providerFlattenedComponentName - ) ?: return@forEach - val (providerLabel, providerIcon) = providerLabelAndIcon - providerList.add( - ProviderInfo( - id = it.providerFlattenedComponentName, - icon = providerIcon, - displayName = providerLabel, - credentialEntryList = getCredentialOptionInfoList( - providerId = it.providerFlattenedComponentName, - providerLabel = providerLabel, - credentialEntries = it.credentialEntries, - context = context - ), - authenticationEntryList = getAuthenticationEntryList( - it.providerFlattenedComponentName, - providerLabel, - providerIcon, - it.authenticationEntries), - remoteEntry = getRemoteEntry( - it.providerFlattenedComponentName, - it.remoteEntry - ), - actionEntryList = getActionEntryList( - it.providerFlattenedComponentName, it.actionChips, providerIcon - ), - ) - ) - } - return providerList - } - + ): List<ProviderInfo> = providerDataList.toProviderList(context) fun toRequestDisplayInfo( requestInfo: RequestInfo?, context: Context, @@ -254,178 +205,6 @@ class GetFlowUtils { preferTopBrandingContent = preferTopBrandingContent, ) } - - - /** - * Note: caller required handle empty list due to parsing error. - */ - private fun getCredentialOptionInfoList( - providerId: String, - providerLabel: String, - credentialEntries: List<Entry>, - context: Context, - ): List<CredentialEntryInfo> { - val result: MutableList<CredentialEntryInfo> = mutableListOf() - credentialEntries.forEach { - val credentialEntry = parseCredentialEntryFromSlice(it.slice) - when (credentialEntry) { - is PasswordCredentialEntry -> { - result.add(CredentialEntryInfo( - providerId = providerId, - providerDisplayName = providerLabel, - entryKey = it.key, - entrySubkey = it.subkey, - pendingIntent = credentialEntry.pendingIntent, - fillInIntent = it.frameworkExtrasIntent, - credentialType = CredentialType.PASSWORD, - credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), - userName = credentialEntry.username.toString(), - displayName = credentialEntry.displayName?.toString(), - icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon, - lastUsedTimeMillis = credentialEntry.lastUsedTime, - isAutoSelectable = credentialEntry.isAutoSelectAllowed && - credentialEntry.autoSelectAllowedFromOption, - )) - } - is PublicKeyCredentialEntry -> { - result.add(CredentialEntryInfo( - providerId = providerId, - providerDisplayName = providerLabel, - entryKey = it.key, - entrySubkey = it.subkey, - pendingIntent = credentialEntry.pendingIntent, - fillInIntent = it.frameworkExtrasIntent, - credentialType = CredentialType.PASSKEY, - credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), - userName = credentialEntry.username.toString(), - displayName = credentialEntry.displayName?.toString(), - icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon, - lastUsedTimeMillis = credentialEntry.lastUsedTime, - isAutoSelectable = credentialEntry.isAutoSelectAllowed && - credentialEntry.autoSelectAllowedFromOption, - )) - } - is CustomCredentialEntry -> { - result.add(CredentialEntryInfo( - providerId = providerId, - providerDisplayName = providerLabel, - entryKey = it.key, - entrySubkey = it.subkey, - pendingIntent = credentialEntry.pendingIntent, - fillInIntent = it.frameworkExtrasIntent, - credentialType = CredentialType.UNKNOWN, - credentialTypeDisplayName = - credentialEntry.typeDisplayName?.toString().orEmpty(), - userName = credentialEntry.title.toString(), - displayName = credentialEntry.subtitle?.toString(), - icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon, - lastUsedTimeMillis = credentialEntry.lastUsedTime, - isAutoSelectable = credentialEntry.isAutoSelectAllowed && - credentialEntry.autoSelectAllowedFromOption, - )) - } - else -> Log.d( - Constants.LOG_TAG, - "Encountered unrecognized credential entry ${it.slice.spec?.type}" - ) - } - } - return result - } - - /** - * @hide - */ - fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? { - try { - when (slice.spec?.type) { - TYPE_PASSWORD_CREDENTIAL -> return PasswordCredentialEntry.fromSlice(slice)!! - TYPE_PUBLIC_KEY_CREDENTIAL -> return PublicKeyCredentialEntry.fromSlice(slice)!! - else -> return CustomCredentialEntry.fromSlice(slice)!! - } - } catch (e: Exception) { - // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed - // password / passkey parsing attempt. - return CustomCredentialEntry.fromSlice(slice) - } - } - - /** - * Note: caller required handle empty list due to parsing error. - */ - private fun getAuthenticationEntryList( - providerId: String, - providerDisplayName: String, - providerIcon: Drawable, - authEntryList: List<AuthenticationEntry>, - ): List<AuthenticationEntryInfo> { - val result: MutableList<AuthenticationEntryInfo> = mutableListOf() - authEntryList.forEach { entry -> - val structuredAuthEntry = - AuthenticationAction.fromSlice(entry.slice) ?: return@forEach - - val title: String = - structuredAuthEntry.title.toString().ifEmpty { providerDisplayName } - - result.add(AuthenticationEntryInfo( - providerId = providerId, - entryKey = entry.key, - entrySubkey = entry.subkey, - pendingIntent = structuredAuthEntry.pendingIntent, - fillInIntent = entry.frameworkExtrasIntent, - title = title, - providerDisplayName = providerDisplayName, - icon = providerIcon, - isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED, - isLastUnlocked = - entry.status == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT - )) - } - return result - } - - private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? { - if (remoteEntry == null) { - return null - } - val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice) - ?: return null - return RemoteEntryInfo( - providerId = providerId, - entryKey = remoteEntry.key, - entrySubkey = remoteEntry.subkey, - pendingIntent = structuredRemoteEntry.pendingIntent, - fillInIntent = remoteEntry.frameworkExtrasIntent, - ) - } - - /** - * Note: caller required handle empty list due to parsing error. - */ - private fun getActionEntryList( - providerId: String, - actionEntries: List<Entry>, - providerIcon: Drawable, - ): List<ActionEntryInfo> { - val result: MutableList<ActionEntryInfo> = mutableListOf() - actionEntries.forEach { - val actionEntryUi = Action.fromSlice(it.slice) ?: return@forEach - result.add(ActionEntryInfo( - providerId = providerId, - entryKey = it.key, - entrySubkey = it.subkey, - pendingIntent = actionEntryUi.pendingIntent, - fillInIntent = it.frameworkExtrasIntent, - title = actionEntryUi.title.toString(), - icon = providerIcon, - subTitle = actionEntryUi.subtitle?.toString(), - )) - } - return result - } } } @@ -686,7 +465,8 @@ class CreateFlowUtils { val result: MutableList<CreateOptionInfo> = mutableListOf() creationEntries.forEach { val createEntry = CreateEntry.fromSlice(it.slice) ?: return@forEach - result.add(CreateOptionInfo( + result.add( + CreateOptionInfo( providerId = providerId, entryKey = it.key, entrySubkey = it.subkey, @@ -705,7 +485,8 @@ class CreateFlowUtils { it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" + "SELECT_ALLOWED") }?.text == "true", - )) + ) + ) } return result.sortedWith( compareByDescending { it.lastUsedTime } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 281696dec717..20d2f09ced8f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -48,10 +48,11 @@ import androidx.credentials.provider.CustomCredentialEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import com.android.credentialmanager.GetFlowUtils -import com.android.credentialmanager.getflow.CredentialEntryInfo +import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.getflow.ProviderDisplayInfo -import com.android.credentialmanager.getflow.ProviderInfo +import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.getflow.toProviderDisplayInfo +import com.android.credentialmanager.ktx.credentialEntry import org.json.JSONObject import java.util.concurrent.Executors @@ -122,8 +123,7 @@ class CredentialAutofillService : AutofillService() { val entryIconMap: MutableMap<String, Icon> = mutableMapOf() candidateProviderDataList.forEach { provider -> provider.credentialEntries.forEach { entry -> - val credentialEntry = GetFlowUtils.parseCredentialEntryFromSlice(entry.slice) - when (credentialEntry) { + when (val credentialEntry = entry.slice.credentialEntry) { is PasswordCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } @@ -172,11 +172,11 @@ class CredentialAutofillService : AutofillService() { } private fun processProvidersForAutofillId( - filLRequest: FillRequest, - autofillId: AutofillId, - providerList: List<ProviderInfo>, - entryIconMap: Map<String, Icon>, - fillResponseBuilder: FillResponse.Builder + filLRequest: FillRequest, + autofillId: AutofillId, + providerList: List<ProviderInfo>, + entryIconMap: Map<String, Icon>, + fillResponseBuilder: FillResponse.Builder ): Boolean { if (providerList.isEmpty()) { return false @@ -200,7 +200,8 @@ class CredentialAutofillService : AutofillService() { providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ { val primaryEntry = it.sortedCredentialEntryList.first() val pendingIntent = primaryEntry.pendingIntent - if (pendingIntent == null || primaryEntry.fillInIntent == null) { + val fillInIntent = primaryEntry.fillInIntent + if (pendingIntent == null || fillInIntent == null) { // FillInIntent will not be null because autofillId was retrieved from it. Log.e(TAG, "PendingIntent was missing from the entry.") return@usernameLoop @@ -245,7 +246,7 @@ class CredentialAutofillService : AutofillService() { presentationBuilder.build()) .build()) .setAuthentication(pendingIntent.intentSender) - .setAuthenticationExtras(primaryEntry.fillInIntent.extras) + .setAuthenticationExtras(fillInIntent.extras) .build()) datasetAdded = true } @@ -322,8 +323,8 @@ class CredentialAutofillService : AutofillService() { } private fun copyProviderInfo( - providerInfo: ProviderInfo, - credentialList: List<CredentialEntryInfo> + providerInfo: ProviderInfo, + credentialList: List<CredentialEntryInfo> ): ProviderInfo { return ProviderInfo( providerInfo.id, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index d45b6f687193..14a91651753b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -48,8 +48,8 @@ import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel import com.android.credentialmanager.R -import com.android.credentialmanager.common.BaseEntry -import com.android.credentialmanager.common.CredentialType +import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetDefaults import com.android.credentialmanager.common.ui.ActionButton @@ -68,6 +68,8 @@ import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.PasskeyBenefitRow import com.android.credentialmanager.common.ui.HeadlineText import com.android.credentialmanager.logging.CreateCredentialEvent +import com.android.credentialmanager.model.creation.CreateOptionInfo +import com.android.credentialmanager.model.creation.RemoteInfo import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme import com.android.internal.logging.UiEventLogger.UiEventEnum @@ -259,15 +261,15 @@ fun PasskeyIntroCard( @Composable fun MoreOptionsSelectionCard( - requestDisplayInfo: RequestDisplayInfo, - enabledProviderList: List<EnabledProviderInfo>, - disabledProviderList: List<DisabledProviderInfo>?, - sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, - onBackCreationSelectionButtonSelected: () -> Unit, - onOptionSelected: (ActiveEntry) -> Unit, - onDisabledProvidersSelected: () -> Unit, - onRemoteEntrySelected: (BaseEntry) -> Unit, - onLog: @Composable (UiEventEnum) -> Unit, + requestDisplayInfo: RequestDisplayInfo, + enabledProviderList: List<EnabledProviderInfo>, + disabledProviderList: List<DisabledProviderInfo>?, + sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, + onBackCreationSelectionButtonSelected: () -> Unit, + onOptionSelected: (ActiveEntry) -> Unit, + onDisabledProvidersSelected: () -> Unit, + onRemoteEntrySelected: (EntryInfo) -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard(topAppBar = { MoreOptionTopAppBar( @@ -378,14 +380,14 @@ fun NonDefaultUsageConfirmationCard( @Composable fun CreationSelectionCard( - requestDisplayInfo: RequestDisplayInfo, - enabledProviderList: List<EnabledProviderInfo>, - providerInfo: EnabledProviderInfo, - createOptionInfo: CreateOptionInfo, - onOptionSelected: (BaseEntry) -> Unit, - onConfirm: () -> Unit, - onMoreOptionsSelected: () -> Unit, - onLog: @Composable (UiEventEnum) -> Unit, + requestDisplayInfo: RequestDisplayInfo, + enabledProviderList: List<EnabledProviderInfo>, + providerInfo: EnabledProviderInfo, + createOptionInfo: CreateOptionInfo, + onOptionSelected: (EntryInfo) -> Unit, + onConfirm: () -> Unit, + onMoreOptionsSelected: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard { item { @@ -474,11 +476,11 @@ fun CreationSelectionCard( @Composable fun ExternalOnlySelectionCard( - requestDisplayInfo: RequestDisplayInfo, - activeRemoteEntry: BaseEntry, - onOptionSelected: (BaseEntry) -> Unit, - onConfirm: () -> Unit, - onLog: @Composable (UiEventEnum) -> Unit, + requestDisplayInfo: RequestDisplayInfo, + activeRemoteEntry: EntryInfo, + onOptionSelected: (EntryInfo) -> Unit, + onConfirm: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard { item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) } @@ -575,17 +577,14 @@ fun MoreAboutPasskeysIntroCard( @Composable fun PrimaryCreateOptionRow( requestDisplayInfo: RequestDisplayInfo, - entryInfo: BaseEntry, - onOptionSelected: (BaseEntry) -> Unit + entryInfo: EntryInfo, + onOptionSelected: (EntryInfo) -> Unit ) { Entry( onClick = { onOptionSelected(entryInfo) }, - iconImageBitmap = - if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) { - entryInfo.profileIcon.toBitmap().asImageBitmap() - } else { - requestDisplayInfo.typeIcon.toBitmap().asImageBitmap() - }, + iconImageBitmap = ((entryInfo as? CreateOptionInfo)?.profileIcon + ?: requestDisplayInfo.typeIcon) + .toBitmap().asImageBitmap(), shouldApplyIconImageBitmapTint = !(entryInfo is CreateOptionInfo && entryInfo.profileIcon != null), entryHeadlineText = requestDisplayInfo.title, @@ -627,32 +626,33 @@ fun MoreOptionsInfoRow( entryThirdLineText = if (requestDisplayInfo.type == CredentialType.PASSKEY || requestDisplayInfo.type == CredentialType.PASSWORD) { - if (createOptionInfo.passwordCount != null && - createOptionInfo.passkeyCount != null - ) { + val passwordCount = createOptionInfo.passwordCount + val passkeyCount = createOptionInfo.passkeyCount + if (passwordCount != null && passkeyCount != null) { stringResource( R.string.more_options_usage_passwords_passkeys, - createOptionInfo.passwordCount, - createOptionInfo.passkeyCount + passwordCount, + passkeyCount ) - } else if (createOptionInfo.passwordCount != null) { + } else if (passwordCount != null) { stringResource( R.string.more_options_usage_passwords, - createOptionInfo.passwordCount + passwordCount ) - } else if (createOptionInfo.passkeyCount != null) { + } else if (passkeyCount != null) { stringResource( R.string.more_options_usage_passkeys, - createOptionInfo.passkeyCount + passkeyCount ) } else { null } } else { - if (createOptionInfo.totalCredentialCount != null) { + val totalCredentialCount = createOptionInfo.totalCredentialCount + if (totalCredentialCount != null) { stringResource( R.string.more_options_usage_credentials, - createOptionInfo.totalCredentialCount + totalCredentialCount ) } else { null diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index e9e8c2e0ccbf..8b0ba87fa9be 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -16,22 +16,21 @@ package com.android.credentialmanager.createflow -import android.app.PendingIntent -import android.content.Intent import android.graphics.drawable.Drawable -import com.android.credentialmanager.common.BaseEntry -import com.android.credentialmanager.common.CredentialType -import java.time.Instant +import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.creation.CreateOptionInfo +import com.android.credentialmanager.model.creation.RemoteInfo data class CreateCredentialUiState( - val enabledProviders: List<EnabledProviderInfo>, - val disabledProviders: List<DisabledProviderInfo>? = null, - val currentScreenState: CreateScreenState, - val requestDisplayInfo: RequestDisplayInfo, - val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, - val activeEntry: ActiveEntry? = null, - val remoteEntry: RemoteInfo? = null, - val foundCandidateFromUserDefaultProvider: Boolean, + val enabledProviders: List<EnabledProviderInfo>, + val disabledProviders: List<DisabledProviderInfo>? = null, + val currentScreenState: CreateScreenState, + val requestDisplayInfo: RequestDisplayInfo, + val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, + val activeEntry: ActiveEntry? = null, + val remoteEntry: RemoteInfo? = null, + val foundCandidateFromUserDefaultProvider: Boolean, ) internal fun isFlowAutoSelectable( @@ -75,44 +74,6 @@ class DisabledProviderInfo( displayName: String, ) : ProviderInfo(icon, id, displayName) -class CreateOptionInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, - val userProviderDisplayName: String, - val profileIcon: Drawable?, - val passwordCount: Int?, - val passkeyCount: Int?, - val totalCredentialCount: Int?, - val lastUsedTime: Instant, - val footerDescription: String?, - val allowAutoSelect: Boolean, -) : BaseEntry( - providerId, - entryKey, - entrySubkey, - pendingIntent, - fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = true, -) - -class RemoteInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, -) : BaseEntry( - providerId, - entryKey, - entrySubkey, - pendingIntent, - fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = true, -) - data class RequestDisplayInfo( val title: String, val subtitle: String?, @@ -131,8 +92,8 @@ data class RequestDisplayInfo( * user selects a different entry on the more option page. */ data class ActiveEntry ( - val activeProvider: EnabledProviderInfo, - val activeEntryInfo: BaseEntry, + val activeProvider: EnabledProviderInfo, + val activeEntryInfo: EntryInfo, ) /** The name of the current screen. */ diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 72d030b3e657..4ed84b908865 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -48,8 +48,9 @@ import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel import com.android.credentialmanager.R -import com.android.credentialmanager.common.BaseEntry -import com.android.credentialmanager.common.CredentialType +import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetDefaults import com.android.credentialmanager.common.ui.ActionButton @@ -68,6 +69,10 @@ import com.android.credentialmanager.common.ui.HeadlineIcon import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.logging.GetCredentialEvent +import com.android.credentialmanager.model.get.ActionEntryInfo +import com.android.credentialmanager.model.get.AuthenticationEntryInfo +import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.model.get.RemoteEntryInfo import com.android.credentialmanager.userAndDisplayNameForPasskey import com.android.internal.logging.UiEventLogger.UiEventEnum @@ -175,8 +180,8 @@ fun PrimarySelectionCard( requestDisplayInfo: RequestDisplayInfo, providerDisplayInfo: ProviderDisplayInfo, providerInfoList: List<ProviderInfo>, - activeEntry: BaseEntry?, - onEntrySelected: (BaseEntry) -> Unit, + activeEntry: EntryInfo?, + onEntrySelected: (EntryInfo) -> Unit, onConfirm: () -> Unit, onMoreOptionSelected: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, @@ -358,7 +363,7 @@ fun PrimarySelectionCard( fun AllSignInOptionCard( providerInfoList: List<ProviderInfo>, providerDisplayInfo: ProviderDisplayInfo, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, onBackButtonClicked: () -> Unit, onCancel: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, @@ -436,7 +441,7 @@ fun HeadlineProviderIconAndName( @Composable fun ActionChips( providerInfoList: List<ProviderInfo>, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, isFirstSection: Boolean, ) { val actionChips = providerInfoList.flatMap { it.actionEntryList } @@ -460,7 +465,7 @@ fun ActionChips( @Composable fun RemoteEntryCard( remoteEntry: RemoteEntryInfo, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, isFirstSection: Boolean, ) { CredentialListSectionHeader( @@ -486,7 +491,7 @@ fun RemoteEntryCard( @Composable fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, isFirstSection: Boolean, ) { CredentialListSectionHeader( @@ -508,7 +513,7 @@ fun LockedCredentials( @Composable fun PerUserNameCredentials( perUserNameCredentialEntryList: PerUserNameCredentialEntryList, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, isFirstSection: Boolean, ) { CredentialListSectionHeader( @@ -532,7 +537,7 @@ fun PerUserNameCredentials( @Composable fun CredentialEntryRow( credentialEntryInfo: CredentialEntryInfo, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, enforceOneLine: Boolean = false, onTextLayout: (TextLayoutResult) -> Unit = {}, ) { @@ -571,7 +576,7 @@ fun CredentialEntryRow( @Composable fun AuthenticationEntryRow( authenticationEntryInfo: AuthenticationEntryInfo, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, enforceOneLine: Boolean = false, ) { Entry( @@ -596,7 +601,7 @@ fun AuthenticationEntryRow( @Composable fun ActionEntryRow( actionEntryInfo: ActionEntryInfo, - onEntrySelected: (BaseEntry) -> Unit, + onEntrySelected: (EntryInfo) -> Unit, ) { ActionEntry( iconImageBitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(), diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 447a9d2aaa8d..46bebc4073ab 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -16,21 +16,21 @@ package com.android.credentialmanager.getflow -import android.app.PendingIntent -import android.content.Intent import android.graphics.drawable.Drawable -import com.android.credentialmanager.common.BaseEntry -import com.android.credentialmanager.common.CredentialType +import com.android.credentialmanager.model.get.ProviderInfo +import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.get.AuthenticationEntryInfo +import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.model.get.RemoteEntryInfo import com.android.internal.util.Preconditions -import java.time.Instant - data class GetCredentialUiState( val providerInfoList: List<ProviderInfo>, val requestDisplayInfo: RequestDisplayInfo, val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList), val currentScreenState: GetScreenState = toGetScreenState(providerDisplayInfo), - val activeEntry: BaseEntry? = toActiveEntry(providerDisplayInfo), + val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo), val isNoAccount: Boolean = false, ) @@ -58,20 +58,6 @@ internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): Cred return null } -data class ProviderInfo( - /** - * Unique id (component name) of this provider. - * Not for display purpose - [displayName] should be used for ui rendering. - */ - val id: String, - val icon: Drawable, - val displayName: String, - val credentialEntryList: List<CredentialEntryInfo>, - val authenticationEntryList: List<AuthenticationEntryInfo>, - val remoteEntry: RemoteEntryInfo?, - val actionEntryList: List<ActionEntryInfo>, -) - /** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping * by the provider id but instead focuses on structures convenient for display purposes. */ data class ProviderDisplayInfo( @@ -84,87 +70,6 @@ data class ProviderDisplayInfo( val remoteEntry: RemoteEntryInfo? ) -class CredentialEntryInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, - /** Type of this credential used for sorting. Not localized so must not be directly displayed. */ - val credentialType: CredentialType, - /** Localized type value of this credential used for display purpose. */ - val credentialTypeDisplayName: String, - val providerDisplayName: String, - val userName: String, - val displayName: String?, - val icon: Drawable?, - val shouldTintIcon: Boolean, - val lastUsedTimeMillis: Instant?, - val isAutoSelectable: Boolean, -) : BaseEntry( - providerId, - entryKey, - entrySubkey, - pendingIntent, - fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = true, -) - -class AuthenticationEntryInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, - val title: String, - val providerDisplayName: String, - val icon: Drawable, - // The entry had been unlocked and turned out to be empty. Used to determine whether to - // show "Tap to unlock" or "No sign-in info" for this entry. - val isUnlockedAndEmpty: Boolean, - // True if the entry was the last one unlocked. Used to show the no sign-in info snackbar. - val isLastUnlocked: Boolean, -) : BaseEntry( - providerId, - entryKey, entrySubkey, - pendingIntent, - fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = false, -) - -class RemoteEntryInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, -) : BaseEntry( - providerId, - entryKey, - entrySubkey, - pendingIntent, - fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = true, -) - -class ActionEntryInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, - val title: String, - val icon: Drawable, - val subTitle: String?, -) : BaseEntry( - providerId, - entryKey, - entrySubkey, - pendingIntent, - fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = true, -) - data class RequestDisplayInfo( val appName: String, val preferImmediatelyAvailableCredentials: Boolean, @@ -218,8 +123,8 @@ fun toProviderDisplayInfo( val remoteEntryList = mutableListOf<RemoteEntryInfo>() providerInfoList.forEach { providerInfo -> authenticationEntryList.addAll(providerInfo.authenticationEntryList) - if (providerInfo.remoteEntry != null) { - remoteEntryList.add(providerInfo.remoteEntry) + providerInfo.remoteEntry?.let { + remoteEntryList.add(it) } // There can only be at most one remote entry Preconditions.checkState(remoteEntryList.size <= 1) @@ -260,11 +165,11 @@ fun toProviderDisplayInfo( private fun toActiveEntry( providerDisplayInfo: ProviderDisplayInfo, -): BaseEntry? { +): EntryInfo? { val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList - var activeEntry: BaseEntry? = null + var activeEntry: EntryInfo? = null if (sortedUserNameToCredentialEntryList .size == 1 && authenticationEntryList.isEmpty() ) { @@ -302,17 +207,18 @@ internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<Cre return 1 } } - + val p0LastUsedTimeMillis = p0.lastUsedTimeMillis + val p1LastUsedTimeMillis = p1.lastUsedTimeMillis // Then order by last used timestamp - if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) { - if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) { + if (p0LastUsedTimeMillis != null && p1LastUsedTimeMillis != null) { + if (p0LastUsedTimeMillis < p1LastUsedTimeMillis) { return 1 - } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) { + } else if (p0LastUsedTimeMillis > p1LastUsedTimeMillis) { return -1 } - } else if (p0.lastUsedTimeMillis != null) { + } else if (p0LastUsedTimeMillis != null) { return -1 - } else if (p1.lastUsedTimeMillis != null) { + } else if (p1LastUsedTimeMillis != null) { return 1 } return 0 diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt index 6ededf3411bf..d8a6019b68a2 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt @@ -1,29 +1,20 @@ package com.android.credentialmanager.di -import android.content.Context -import android.content.pm.PackageManager import com.android.credentialmanager.client.CredentialManagerClient import com.android.credentialmanager.client.impl.CredentialManagerClientImpl +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object AppModule { - @Provides +abstract class AppModule { + @Binds @Singleton - @JvmStatic - fun providePackageManager(@ApplicationContext context: Context): PackageManager = - context.packageManager - - @Provides - @Singleton - @JvmStatic - fun provideCredentialManagerClient(packageManager: PackageManager): CredentialManagerClient = - CredentialManagerClientImpl(packageManager) + abstract fun provideCredentialManagerClient( + client: CredentialManagerClientImpl + ): CredentialManagerClient } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt index f2f878e8ac2f..14b992a0d0e9 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt @@ -23,8 +23,8 @@ fun Request.Get.toGet(): CredentialSelectorUiState.Get { // TODO: b/301206470 returning a hard coded state for MVP if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword - return if (providers.size == 1) { - if (passwordEntries.size == 1) { + return if (providerInfos.size == 1) { + if (providerInfos.first().credentialEntryList.size == 1) { CredentialSelectorUiState.Get.SingleProviderSinglePassword } else { TODO() // b/301206470 - Implement other get flows diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt index c28df3e8895a..b64f58192d23 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt @@ -76,7 +76,9 @@ fun SinglePasswordScreen( } SideEffect { - launcher.launch(state.intentSenderRequest) + state.intentSenderRequest?.let { + launcher.launch(it) + } } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt index fb72c544c978..26bee1f8d191 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt @@ -26,9 +26,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.android.credentialmanager.TAG import com.android.credentialmanager.ktx.getIntentSenderRequest -import com.android.credentialmanager.model.Password import com.android.credentialmanager.model.Request import com.android.credentialmanager.client.CredentialManagerClient +import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.model.PasswordUiModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -44,7 +44,7 @@ class SinglePasswordScreenViewModel @Inject constructor( private var initializeCalled = false private lateinit var requestGet: Request.Get - private lateinit var password: Password + private lateinit var entryInfo: CredentialEntryInfo private val _uiState = MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle) @@ -63,14 +63,15 @@ class SinglePasswordScreenViewModel @Inject constructor( _uiState.value = SinglePasswordScreenUiState.Error } else { requestGet = request - if (requestGet.passwordEntries.isEmpty()) { + + if (requestGet.providerInfos.all { it.credentialEntryList.isEmpty() }) { Log.d(TAG, "Empty passwordEntries") _uiState.value = SinglePasswordScreenUiState.Error } else { - password = requestGet.passwordEntries.first() + entryInfo = requestGet.providerInfos.first().credentialEntryList.first() _uiState.value = SinglePasswordScreenUiState.Loaded( PasswordUiModel( - email = password.passwordCredentialEntry.username.toString(), + email = entryInfo.userName, ) ) } @@ -84,7 +85,7 @@ class SinglePasswordScreenViewModel @Inject constructor( fun onOKClick() { _uiState.value = SinglePasswordScreenUiState.PasswordSelected( - intentSenderRequest = password.getIntentSenderRequest() + intentSenderRequest = entryInfo.getIntentSenderRequest() ) } @@ -94,9 +95,9 @@ class SinglePasswordScreenViewModel @Inject constructor( ) { val userSelectionDialogResult = UserSelectionDialogResult( requestGet.token, - password.providerId, - password.entry.key, - password.entry.subkey, + entryInfo.providerId, + entryInfo.entryKey, + entryInfo.entrySubkey, if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null ) credentialManagerClient.sendResult(userSelectionDialogResult) @@ -108,7 +109,7 @@ sealed class SinglePasswordScreenUiState { data object Idle : SinglePasswordScreenUiState() data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState() data class PasswordSelected( - val intentSenderRequest: IntentSenderRequest + val intentSenderRequest: IntentSenderRequest? ) : SinglePasswordScreenUiState() data object Cancel : SinglePasswordScreenUiState() diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 38bd7d5f3944..6213b34034af 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -49,6 +49,7 @@ android_app { "androidx.fragment_fragment", "androidx.lifecycle_lifecycle-livedata", "androidx.lifecycle_lifecycle-extensions", + "android.content.pm.flags-aconfig-java", ], lint: { @@ -75,6 +76,7 @@ android_app { "androidx.fragment_fragment", "androidx.lifecycle_lifecycle-livedata", "androidx.lifecycle_lifecycle-extensions", + "android.content.pm.flags-aconfig-java", ], aaptflags: ["--product tablet"], @@ -103,6 +105,7 @@ android_app { "androidx.fragment_fragment", "androidx.lifecycle_lifecycle-livedata", "androidx.lifecycle_lifecycle-extensions", + "android.content.pm.flags-aconfig-java", ], aaptflags: ["--product tv"], diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java index 19d74b33e034..7b17cbdd3a1e 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java @@ -16,8 +16,6 @@ package com.android.packageinstaller; -import static android.content.Intent.CATEGORY_LAUNCHER; - import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; import android.app.Activity; @@ -47,9 +45,6 @@ public class DeleteStagedFileOnResult extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { setResult(resultCode, data); finish(); - if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) { - startActivity(data); - } } @Override diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index e2107ebe2525..418705845065 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -16,12 +16,14 @@ package com.android.packageinstaller; +import static android.content.pm.Flags.usePiaV2; import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; import android.Manifest; import android.app.Activity; import android.app.DialogFragment; import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -57,14 +59,21 @@ public class InstallStart extends Activity { private final boolean mLocalLOGV = false; - // TODO (sumedhsen): Replace with an Android Feature Flag once implemented - private static final boolean USE_PIA_V2 = false; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (USE_PIA_V2) { + mPackageManager = getPackageManager(); + if (usePiaV2()) { + Log.i(TAG, "Using Pia V2"); + + mPackageManager.setComponentEnabledSetting(new ComponentName(this, + com.android.packageinstaller.InstallEventReceiver.class), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0); + mPackageManager.setComponentEnabledSetting(new ComponentName(this, + com.android.packageinstaller.v2.model.InstallEventReceiver.class), + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + Intent piaV2 = new Intent(getIntent()); piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_NAME, getCallingPackage()); piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid()); @@ -74,7 +83,6 @@ public class InstallStart extends Activity { finish(); return; } - mPackageManager = getPackageManager(); mUserManager = getSystemService(UserManager.class); Intent intent = getIntent(); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java index 9af88c3b4694..215ead367148 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java @@ -18,6 +18,7 @@ package com.android.packageinstaller; import android.app.Activity; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -120,7 +121,12 @@ public class InstallSuccess extends Activity { Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE); if (enabled) { launchButton.setOnClickListener(view -> { - setResult(Activity.RESULT_OK, mLaunchIntent); + try { + startActivity(mLaunchIntent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)); + } catch (ActivityNotFoundException | SecurityException e) { + Log.e(LOG_TAG, "Could not start activity", e); + } finish(); }); } else { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 34062a4cbde6..ba627e9e9202 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -17,8 +17,8 @@ package com.android.packageinstaller; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.content.pm.Flags.usePiaV2; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; - import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; import android.Manifest; @@ -81,9 +81,6 @@ public class UninstallerActivity extends Activity { private String mPackageName; private DialogInfo mDialogInfo; - // TODO (sumedhsen): Replace with an Android Feature Flag once implemented - private static final boolean USE_PIA_V2 = false; - @Override public void onCreate(Bundle icicle) { getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); @@ -92,7 +89,18 @@ public class UninstallerActivity extends Activity { // be stale, if e.g. the app was uninstalled while the activity was destroyed. super.onCreate(null); - if (USE_PIA_V2 && !isTv()) { + if (usePiaV2() && !isTv()) { + Log.i(TAG, "Using Pia V2"); + + PackageManager pm = getPackageManager(); + pm.setComponentEnabledSetting( + new ComponentName(this, com.android.packageinstaller.UninstallEventReceiver.class), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0); + pm.setComponentEnabledSetting( + new ComponentName(this, + com.android.packageinstaller.v2.model.UninstallEventReceiver.class), + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); Intent piaV2 = new Intent(getIntent()); piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid()); diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp index 4871ef3097a5..010a6ce9d4d9 100644 --- a/packages/SettingsLib/MainSwitchPreference/Android.bp +++ b/packages/SettingsLib/MainSwitchPreference/Android.bp @@ -17,7 +17,6 @@ android_library { static_libs: [ "androidx.preference_preference", "SettingsLibSettingsTheme", - "SettingsLibUtils", ], sdk_version: "system_current", diff --git a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml index 4b3acbf2c394..e70114f36e97 100644 --- a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml +++ b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml @@ -17,5 +17,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib.widget.mainswitch"> - + <uses-sdk android:minSdkVersion="21" /> </manifest> diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 2b5fcd807899..e6f61a8400c4 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -18,6 +18,8 @@ package com.android.settingslib.widget; import android.content.Context; import android.content.res.TypedArray; +import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -30,7 +32,6 @@ import android.widget.TextView; import androidx.annotation.ColorInt; -import com.android.settingslib.utils.BuildCompatUtils; import com.android.settingslib.widget.mainswitch.R; import java.util.ArrayList; @@ -72,11 +73,18 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen LayoutInflater.from(context).inflate(R.layout.settingslib_main_switch_bar, this); - if (!BuildCompatUtils.isAtLeastS()) { - final TypedArray a = context.obtainStyledAttributes( - new int[]{android.R.attr.colorAccent}); - mBackgroundActivatedColor = a.getColor(0, 0); - mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600); + if (Build.VERSION.SDK_INT < VERSION_CODES.S) { + TypedArray a; + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { + a = context.obtainStyledAttributes( + new int[]{android.R.attr.colorAccent}); + mBackgroundActivatedColor = a.getColor(0, 0); + mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600); + } else { + a = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary}); + mBackgroundActivatedColor = a.getColor(0, 0); + mBackgroundColor = a.getColor(0, 0); + } a.recycle(); } @@ -148,7 +156,7 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen * Set icon space reserved for title */ public void setIconSpaceReserved(boolean iconSpaceReserved) { - if (mTextView != null && !BuildCompatUtils.isAtLeastS()) { + if (mTextView != null && (Build.VERSION.SDK_INT < VERSION_CODES.S)) { LayoutParams params = (LayoutParams) mTextView.getLayoutParams(); int iconSpace = getContext().getResources().getDimensionPixelSize( R.dimen.settingslib_switchbar_subsettings_margin_start); @@ -207,7 +215,7 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen mTextView.setEnabled(enabled); mSwitch.setEnabled(enabled); - if (BuildCompatUtils.isAtLeastS()) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.S) { mFrameView.setEnabled(enabled); mFrameView.setActivated(isChecked()); } @@ -222,7 +230,7 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen } private void setBackground(boolean isChecked) { - if (!BuildCompatUtils.isAtLeastS()) { + if (Build.VERSION.SDK_INT < VERSION_CODES.S) { setBackgroundColor(isChecked ? mBackgroundActivatedColor : mBackgroundColor); } else { mFrameView.setActivated(isChecked); diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 80b944f5749f..60bf48c8b75e 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -25,11 +25,11 @@ plugins { alias(libs.plugins.kotlin.android) apply false } -val androidTop : String = File(rootDir, "../../../../..").canonicalPath +val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.6.0-alpha08" + extra["jetpackComposeVersion"] = "1.6.0-beta01" } subprojects { diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index 4335173dd706..acd90f3c4f4d 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,13 +57,13 @@ dependencies { api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.2.0-alpha10") + api("androidx.compose.material3:material3:1.2.0-alpha11") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.7.5") + api("androidx.navigation:navigation-compose:2.7.4") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.7.0-alpha03") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt index 26372b6bfd32..c395558b769c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt @@ -17,9 +17,7 @@ package com.android.settingslib.spa.framework.theme import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.ripple.LocalRippleTheme -import androidx.compose.material.ripple.RippleAlpha -import androidx.compose.material.ripple.RippleTheme +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -39,7 +37,7 @@ fun SettingsTheme(content: @Composable () -> Unit) { MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) { CompositionLocalProvider( LocalColorScheme provides settingsColorScheme(isDarkTheme), - LocalRippleTheme provides SettingsRippleTheme, + LocalContentColor provides MaterialTheme.colorScheme.onSurface, ) { content() } @@ -52,19 +50,3 @@ object SettingsTheme { @ReadOnlyComposable get() = LocalColorScheme.current } - -private object SettingsRippleTheme : RippleTheme { - @Composable - override fun defaultColor() = MaterialTheme.colorScheme.onSurface - - @Composable - override fun rippleAlpha() = RippleAlpha -} - -/** Alpha levels for all content. */ -private val RippleAlpha = RippleAlpha( - pressedAlpha = 0.48f, - focusedAlpha = 0.48f, - draggedAlpha = 0.32f, - hoveredAlpha = 0.16f, -) diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java index 0a2d9fc3372e..e41126f03c60 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -140,12 +140,6 @@ public class PrimarySwitchPreference extends RestrictedPreference { } } - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - setSwitchEnabled(enabled); - } - @VisibleForTesting(otherwise = VisibleForTesting.NONE) public boolean isSwitchEnabled() { return mEnableSwitch; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 8b0f19d455d2..29ea25e13835 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -22,7 +22,6 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.os.Build; import android.os.UserHandle; import android.text.TextUtils; @@ -237,15 +236,13 @@ public class RestrictedPreferenceHelper { } private void updateDisabledState() { - boolean isEnabled = !(mDisabledByAdmin || mDisabledByAppOps); if (!(mPreference instanceof RestrictedTopLevelPreference)) { - mPreference.setEnabled(isEnabled); + mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); } - Drawable icon = mPreference.getIcon(); - if (!isEnabled && icon != null) { - Utils.convertToGrayscale(icon); - mPreference.setIcon(icon); + if (mPreference instanceof PrimarySwitchPreference) { + ((PrimarySwitchPreference) mPreference) + .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index f03263b71138..107d5f8a8ae9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -728,14 +728,4 @@ public class Utils { return false; } - /** - * Convert a drawable to grayscale drawable - */ - public static void convertToGrayscale(@NonNull Drawable drawable) { - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0.0f); - - ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix); - drawable.setColorFilter(filter); - } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index efed8c3c1ef4..85e87691ac85 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -235,6 +235,7 @@ public class SettingsBackupTest { Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH, Settings.Global.DEVICE_DEMO_MODE, + Settings.Global.DEVICE_IDLE_CONSTANTS, Settings.Global.DISABLE_WINDOW_BLURS, Settings.Global.BATTERY_SAVER_CONSTANTS, Settings.Global.BATTERY_TIP_CONSTANTS, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index d78038ecee61..7cf562f48ff3 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -242,184 +242,10 @@ filegroup { } filegroup { - name: "SystemUI-test-fakes", - srcs: [ - /* Status bar fakes */ - "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt", - "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt", - "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt", - "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt", - - /* QS fakes */ - "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt", - ], - path: "tests/src", -} - -filegroup { - name: "SystemUI-tests-robolectric-pilots", - srcs: [ - /* Keyguard converted tests */ - // data - "tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt", - "tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt", - // domain - "tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt", - "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt", - "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt", - "tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt", - "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt", - "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt", - "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt", - "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt", - "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt", - // ui - "tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt", - // Keyguard helper - "tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt", - "tests/src/com/android/systemui/dock/DockManagerFake.java", - "tests/src/com/android/systemui/dump/LogBufferHelper.kt", - "tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java", - - /* Biometric converted tests */ - "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt", - "tests/src/com/android/systemui/biometrics/AuthControllerTest.java", - "tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java", - "tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt", - "tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt", - "tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt", - "tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java", - "tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java", - "tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java", - "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java", - "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java", - "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt", - "tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt", - "tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt", - - /* Status bar wifi converted tests */ - "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt", - "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt", - "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt", - "tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt", - "tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt", - "tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt", - "tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt", - - /* Bouncer UI tests */ - "tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt", - "tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt", - "tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java", - "tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt", - "tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java", - "tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt", - "tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java", - "tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt", - "tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt", - - /* Communal tests */ - "tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt", - "tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt", - "tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt", - - /* Dream tests */ - "tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java", - "tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java", - "tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java", - "tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java", - "tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java", - "tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java", - "tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java", - "tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt", - "tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt", - "tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java", - "tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java", - "tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java", - "tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java", - "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java", - "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java", - - /* Smartspace tests */ - "tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt", - "tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt", - "tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt", - "tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt", - - /* Quick Settings new pipeline converted tests */ - "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt", - "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt", - "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt", - "tests/src/com/android/systemui/qs/tiles/base/**/*.kt", - "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt", - "tests/src/com/android/systemui/qs/tiles/impl/**/*.kt", - - /* Authentication */ - "tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt", - "tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt", - - /* Device entry */ - "tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt", - "tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt", - - /* Bouncer scene */ - "tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt", - "tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt", - "tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt", - "tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt", - "tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt", - "tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt", - "tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt", - - /* Lockscreen scene */ - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt", - - /* Shade scene */ - "tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt", - "tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt", - - /* Quick Settings scene */ - "tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt", - - /* Flexiglass / Scene framework tests */ - "tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt", - "tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt", - "tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt", - "tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt", - "tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt", - "tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt", - "tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt", - - ], - path: "tests/src", -} - -filegroup { name: "SystemUI-tests-multivalent", srcs: [ "multivalentTests/src/**/*.kt", + "multivalentTests/src/**/*.java", ], path: "multivalentTests/src", } @@ -597,8 +423,6 @@ android_robolectric_test { "tests/robolectric/src/**/*.kt", "tests/robolectric/src/**/*.java", ":SystemUI-tests-utils", - ":SystemUI-test-fakes", - ":SystemUI-tests-robolectric-pilots", ":SystemUI-tests-multivalent", ], static_libs: [ diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 0480b9dbfc8e..0c89a5dcbcf4 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -126,24 +126,6 @@ "exclude-annotation": "android.platform.test.annotations.Postsubmit" } ] - }, - { - // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+) - "name": "SystemUIGoogleBiometricsScreenshotTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.Postsubmit" - } - ] } ], @@ -170,18 +152,6 @@ "include-annotation": "androidx.test.filters.FlakyTest" } ] - }, - { - // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+) - "name": "SystemUIGoogleBiometricsScreenshotTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-annotation": "androidx.test.filters.FlakyTest" - } - ] } ] } diff --git a/packages/SystemUI/aconfig/OWNERS b/packages/SystemUI/aconfig/OWNERS index e1a7a0f84ad0..902ba907a100 100644 --- a/packages/SystemUI/aconfig/OWNERS +++ b/packages/SystemUI/aconfig/OWNERS @@ -1 +1,2 @@ per-file accessibility.aconfig = file:/core/java/android/view/accessibility/OWNERS +per-file biometrics_framework.aconfig = file:/services/core/java/com/android/server/biometrics/OWNERS diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig new file mode 100644 index 000000000000..5fd3b485e9ed --- /dev/null +++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig @@ -0,0 +1,10 @@ +package: "com.android.systemui" + +# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. + +flag { + name: "bp_talkback" + namespace: "biometrics_framework" + description: "Adds talkback directional guidance when using UDFPS with biometric prompt" + bug: "310044658" +}
\ No newline at end of file diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 17620651128f..3e84597a375d 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -164,3 +164,9 @@ flag { bug: "296122467" } +flag { + name: "record_issue_qs_tile" + namespace: "systemui" + description: "Replace Record Trace QS Tile with expanded Record Issue QS Tile" + bug: "305049544" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 0b1338305076..eb0688914b9d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -16,12 +16,10 @@ package com.android.systemui.bouncer.ui.composable +import android.view.ViewTreeObserver import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.LocalTextStyle @@ -30,46 +28,56 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.core.view.WindowInsetsCompat import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel /** UI for the input part of a password-requiring version of the bouncer. */ -@OptIn(ExperimentalLayoutApi::class) @Composable internal fun PasswordBouncer( viewModel: PasswordBouncerViewModel, modifier: Modifier = Modifier, ) { val focusRequester = remember { FocusRequester() } + val isTextFieldFocusRequested by viewModel.isTextFieldFocusRequested.collectAsState() + LaunchedEffect(isTextFieldFocusRequested) { + if (isTextFieldFocusRequested) { + focusRequester.requestFocus() + } + } + val (isTextFieldFocused, onTextFieldFocusChanged) = remember { mutableStateOf(false) } + LaunchedEffect(isTextFieldFocused) { + viewModel.onTextFieldFocusChanged(isFocused = isTextFieldFocused) + } + val password: String by viewModel.password.collectAsState() val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() - val density = LocalDensity.current - val isImeVisible by rememberUpdatedState(WindowInsets.imeAnimationTarget.getBottom(density) > 0) + val isImeVisible by isSoftwareKeyboardVisible() LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) } DisposableEffect(Unit) { viewModel.onShown() - - // When the UI comes up, request focus on the TextField to bring up the software keyboard. - focusRequester.requestFocus() - onDispose { viewModel.onHidden() } } @@ -104,16 +112,39 @@ internal fun PasswordBouncer( onDone = { viewModel.onAuthenticateKeyPressed() }, ), modifier = - Modifier.focusRequester(focusRequester).drawBehind { - drawLine( - color = color, - start = Offset(x = 0f, y = size.height - lineWidthPx), - end = Offset(size.width, y = size.height - lineWidthPx), - strokeWidth = lineWidthPx, - ) - }, + Modifier.focusRequester(focusRequester) + .onFocusChanged { onTextFieldFocusChanged(it.isFocused) } + .drawBehind { + drawLine( + color = color, + start = Offset(x = 0f, y = size.height - lineWidthPx), + end = Offset(size.width, y = size.height - lineWidthPx), + strokeWidth = lineWidthPx, + ) + }, ) Spacer(Modifier.height(100.dp)) } } + +/** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */ +@Composable +fun isSoftwareKeyboardVisible(): State<Boolean> { + val view = LocalView.current + val viewTreeObserver = view.viewTreeObserver + + return produceState( + initialValue = false, + key1 = viewTreeObserver, + ) { + val listener = + ViewTreeObserver.OnGlobalLayoutListener { + value = view.rootWindowInsets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false + } + + viewTreeObserver.addOnGlobalLayoutListener(listener) + + awaitDispose { viewTreeObserver.removeOnGlobalLayoutListener(listener) } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index 3fbcf6d77f82..3fbcf6d77f82 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 74c922561343..74c922561343 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index d41c2497b230..d41c2497b230 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 80d45bcc23dd..80d45bcc23dd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 543b2910bbda..543b2910bbda 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 156e06843d15..156e06843d15 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 7bb6ef1c8895..7bb6ef1c8895 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 94c3bde29597..94c3bde29597 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 7b1f302da6e8..7b1f302da6e8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 64ddbc7828ac..64ddbc7828ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 56d3d260d196..56d3d260d196 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index 602f3dc29491..602f3dc29491 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java index 714461b715d6..714461b715d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 15633d1baed1..15633d1baed1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt index 39f0d570cb26..39f0d570cb26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index a1b801cd3d3f..a1b801cd3d3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index f5b6f14a627c..f5b6f14a627c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index e2cab29c473c..e2cab29c473c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java index cd9189bef7f1..cd9189bef7f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java index 5239966f1923..5239966f1923 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java index 2ea803c6aa8f..2ea803c6aa8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java index 98d8b054716c..98d8b054716c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 79f062536404..79f062536404 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsShellTest.kt index 8b374ae54127..8b374ae54127 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsShellTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 9fbe09619ff1..9fbe09619ff1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt index b391b5a45799..b391b5a45799 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index f0d26b6bbb78..f0d26b6bbb78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 1e8073246f98..83fb17fa50e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -319,10 +319,10 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun imeHiddenEvent_isTriggered() = testScope.runTest { - val imeHiddenEvent by collectLastValue(underTest.onImeHidden) + val imeHiddenEvent by collectLastValue(underTest.onImeHiddenByUser) runCurrent() - underTest.onImeHidden() + underTest.onImeHiddenByUser() runCurrent() assertThat(imeHiddenEvent).isNotNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt index 4aea4f329858..4aea4f329858 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt index bdf5041f8a38..bdf5041f8a38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt index 8c53c0e3f267..8c53c0e3f267 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 63c992bd7854..45c186dc3a77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -23,8 +23,6 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @@ -76,18 +74,4 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(animateFailure).isFalse() } - - @Test - fun onImeVisibilityChanged() = - testScope.runTest { - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "") - val onImeHidden by collectLastValue(bouncerInteractor.onImeHidden) - - underTest.onImeVisibilityChanged(true) - assertThat(onImeHidden).isNull() - - underTest.onImeVisibilityChanged(false) - assertThat(onImeHidden).isNotNull() - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 75d6a007b4aa..75d6a007b4aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 90e0c19b7c65..90e0c19b7c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 9b1e9585979a..937c703d6775 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -20,7 +20,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -43,7 +45,11 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() + private val authenticationRepository = utils.authenticationRepository + private val authenticationInteractor = + utils.authenticationInteractor( + repository = authenticationRepository, + ) private val sceneInteractor = utils.sceneInteractor() private val bouncerInteractor = utils.bouncerInteractor( @@ -207,6 +213,101 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } + @Test + fun onImeVisibilityChanged_false_doesNothing() = + testScope.runTest { + val events by collectValues(bouncerInteractor.onImeHiddenByUser) + assertThat(events).isEmpty() + + underTest.onImeVisibilityChanged(isVisible = false) + assertThat(events).isEmpty() + } + + @Test + fun onImeVisibilityChanged_falseAfterTrue_emitsOnImeHiddenByUserEvent() = + testScope.runTest { + val events by collectValues(bouncerInteractor.onImeHiddenByUser) + assertThat(events).isEmpty() + + underTest.onImeVisibilityChanged(isVisible = true) + assertThat(events).isEmpty() + + underTest.onImeVisibilityChanged(isVisible = false) + assertThat(events).hasSize(1) + + underTest.onImeVisibilityChanged(isVisible = true) + assertThat(events).hasSize(1) + + underTest.onImeVisibilityChanged(isVisible = false) + assertThat(events).hasSize(2) + } + + @Test + fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() = + testScope.runTest { + val events by collectValues(bouncerInteractor.onImeHiddenByUser) + assertThat(events).isEmpty() + underTest.onImeVisibilityChanged(isVisible = true) + setThrottling(true) + + underTest.onImeVisibilityChanged(isVisible = false) + + assertThat(events).isEmpty() + } + + @Test + fun isTextFieldFocusRequested_initiallyTrue() = + testScope.runTest { + val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + assertThat(isTextFieldFocusRequested).isTrue() + } + + @Test + fun isTextFieldFocusRequested_focusGained_becomesFalse() = + testScope.runTest { + val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + + underTest.onTextFieldFocusChanged(isFocused = true) + + assertThat(isTextFieldFocusRequested).isFalse() + } + + @Test + fun isTextFieldFocusRequested_focusLost_becomesTrue() = + testScope.runTest { + val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + underTest.onTextFieldFocusChanged(isFocused = true) + + underTest.onTextFieldFocusChanged(isFocused = false) + + assertThat(isTextFieldFocusRequested).isTrue() + } + + @Test + fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() = + testScope.runTest { + val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + underTest.onTextFieldFocusChanged(isFocused = true) + setThrottling(true) + + underTest.onTextFieldFocusChanged(isFocused = false) + + assertThat(isTextFieldFocusRequested).isFalse() + } + + @Test + fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() = + testScope.runTest { + val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + underTest.onTextFieldFocusChanged(isFocused = true) + setThrottling(true) + underTest.onTextFieldFocusChanged(isFocused = false) + + setThrottling(false) + + assertThat(isTextFieldFocusRequested).isTrue() + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.desiredScene) val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer @@ -226,6 +327,35 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { switchToScene(SceneKey.Bouncer) } + private suspend fun TestScope.setThrottling( + isThrottling: Boolean, + failedAttemptCount: Int = 5, + ) { + if (isThrottling) { + repeat(failedAttemptCount) { + authenticationRepository.reportAuthenticationAttempt(false) + } + val remainingTimeMs = 30_000 + authenticationRepository.setThrottleDuration(remainingTimeMs) + authenticationRepository.setThrottling( + AuthenticationThrottlingModel( + failedAttemptCount = failedAttemptCount, + remainingMs = remainingTimeMs, + ) + ) + } else { + authenticationRepository.reportAuthenticationAttempt(true) + authenticationRepository.setThrottling( + AuthenticationThrottlingModel( + failedAttemptCount = failedAttemptCount, + remainingMs = 0, + ) + ) + } + + runCurrent() + } + companion object { private const val ENTER_YOUR_PASSWORD = "Enter your password" private const val WRONG_PASSWORD = "Wrong password" diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 862c39c9d4cc..862c39c9d4cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index c30e405ab911..c30e405ab911 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt index 55016bb1fc07..55016bb1fc07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 182712a13174..182712a13174 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 16cfa2398fd5..16cfa2398fd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index a49629252520..a49629252520 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 97ac8c62d69d..97ac8c62d69d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 0004f52bc1c1..910097eece52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository @@ -56,6 +57,13 @@ class DeviceEntryInteractorTest : SysuiTestCase() { ) @Test + fun canSwipeToEnter_startsNull() = + testScope.runTest { + val values by collectValues(underTest.canSwipeToEnter) + assertThat(values[0]).isNull() + } + + @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java index 37540621557f..37540621557f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index 8a35ef11a364..8a35ef11a364 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt index 2c6c793c97f5..2c6c793c97f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 2af6566e993a..2af6566e993a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java index d379dc6f3dc1..d379dc6f3dc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index e5f997257cfa..e5f997257cfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 6d5cd49b8af6..6d5cd49b8af6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java index 7ff345cdcf7e..7ff345cdcf7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 39db2beb4c44..39db2beb4c44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java index 315a24bfd945..315a24bfd945 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java index e0c6ab20c6e1..e0c6ab20c6e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java index 480754c17616..480754c17616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index 3d1efa59a11b..3d1efa59a11b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java index 6aa821f15ab1..6aa821f15ab1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java index 017fdbe8ac01..017fdbe8ac01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java index 4ee4a60fbeaf..4ee4a60fbeaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogBufferHelper.kt index 0538227abd3f..0538227abd3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogBufferHelper.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index 977f1db44258..977f1db44258 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt index 548b5646f5d4..548b5646f5d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt index 4ae144c03314..4ae144c03314 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt index 7d68cc0a3560..7d68cc0a3560 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 26fcb234843d..26fcb234843d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index 99a01858471c..99a01858471c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt index a1c9f87ee7bc..a1c9f87ee7bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt index b15352bfe6ab..b15352bfe6ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt index 521dea34513e..521dea34513e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index 02db0d7a9a50..02db0d7a9a50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt index a9b9c9011636..a9b9c9011636 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 4587ea6dbdc8..4587ea6dbdc8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 45aca175657e..45aca175657e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index 2b7221ec192c..2b7221ec192c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt index ae6c5b7b36b0..ae6c5b7b36b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 567e0a9717fc..567e0a9717fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 6c4bb372bc3a..6c4bb372bc3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index 7242cb20dc77..7242cb20dc77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt index ee47c58f4002..ee47c58f4002 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 706f94e412ac..706f94e412ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index 98f0211587ea..98f0211587ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index bc4bae0ed959..bc4bae0ed959 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 4f7d9444020c..4f7d9444020c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index b483085cf1e5..b483085cf1e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index fd125e099f1b..fd125e099f1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index cf2012989624..cf2012989624 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index d07836d3abce..d07836d3abce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index ba72b4c95a44..ba72b4c95a44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 3536d5c77c93..3536d5c77c93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index d0772270ed5e..d0772270ed5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 6cab023d59b0..6cab023d59b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt index d277fcab3690..d277fcab3690 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt index 3db676d68f42..3db676d68f42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt index 070e07a75d23..070e07a75d23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt index ff8a9bd019fb..ff8a9bd019fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt index f7c3b213730c..f7c3b213730c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt index 9516c2181ac0..9516c2181ac0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt index 36e860e37ffa..36e860e37ffa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt index d4a9fabd6806..d4a9fabd6806 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt index 4454a3cb15fc..4454a3cb15fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt index d153e9d1d361..d153e9d1d361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt index ec139e4c515e..ec139e4c515e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt index 4fae532d4174..4fae532d4174 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt index 9e2d1f885e2d..9e2d1f885e2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt index 0116bd9575d8..0116bd9575d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt index e7ea9a66450c..e7ea9a66450c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt index 20fd3601f9ef..20fd3601f9ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt index 19ac63c36cab..19ac63c36cab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt index d645ee34619b..d645ee34619b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt index 83ff35d8022d..83ff35d8022d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt index adccc84e494b..adccc84e494b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt index 41a7ec03408d..41a7ec03408d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 8c896a6a1709..8c896a6a1709 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt index 7e0e7d1f46e8..7e0e7d1f46e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt index 0d9711588a1f..0d9711588a1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt index f73cab8a10a3..f73cab8a10a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt index 558e7694b54c..558e7694b54c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt index bd1c310ab8de..bd1c310ab8de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt index 9861606fd1b1..9861606fd1b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt index 2bdc154dd885..2bdc154dd885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index 92c2d743c262..92c2d743c262 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt index 937744db500e..937744db500e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt index 81bde8188f5e..81bde8188f5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt index 89ba69fce9ad..89ba69fce9ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt index 4a221134ce67..4a221134ce67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt new file mode 100644 index 000000000000..cf076c557765 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2023 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.impl.custom.data.repository + +import android.content.ComponentName +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.external.FakeCustomTileStatePersister +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.commons.copy +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class CustomTileRepositoryTest : SysuiTestCase() { + + private val testScope = TestScope() + + private val persister = FakeCustomTileStatePersister() + + private val underTest: CustomTileRepository = + CustomTileRepositoryImpl( + TileSpec.create(TEST_COMPONENT), + persister, + testScope.testScheduler, + ) + + @Test + fun persistableTileIsRestoredForUser() = + testScope.runTest { + persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1) + persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2) + + underTest.restoreForTheUserIfNeeded(TEST_USER_1, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1) + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1) + } + + @Test + fun notPersistableTileIsNotRestored() = + testScope.runTest { + persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1) + val tiles = collectValues(underTest.getTiles(TEST_USER_1)) + + underTest.restoreForTheUserIfNeeded(TEST_USER_1, false) + runCurrent() + + assertThat(tiles()).isEmpty() + } + + @Test + fun emptyPersistedStateIsHandled() = + testScope.runTest { + val tiles = collectValues(underTest.getTiles(TEST_USER_1)) + + underTest.restoreForTheUserIfNeeded(TEST_USER_1, true) + runCurrent() + + assertThat(tiles()).isEmpty() + } + + @Test + fun updatingWithPersistableTilePersists() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun updatingWithNotPersistableTileDoesntPersist() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isNull() + } + + @Test + fun updateWithTileEmits() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1) + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun updatingPeristableWithDefaultsPersists() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun updatingNotPersistableWithDefaultsDoesntPersist() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isNull() + } + + @Test + fun updatingPeristableWithErrorDefaultsDoesntPersist() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isNull() + } + + @Test + fun updateWithDefaultsEmits() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1) + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun getTileForAnotherUserReturnsNull() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_2)).isNull() + } + + @Test + fun getTilesForAnotherUserEmpty() = + testScope.runTest { + val tiles = collectValues(underTest.getTiles(TEST_USER_2)) + + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(tiles()).isEmpty() + } + + @Test + fun updatingWithTileForTheSameUserAddsData() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true) + runCurrent() + + val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" } + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile) + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile) + } + + @Test + fun updatingWithTileForAnotherUserOverridesTile() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + val tiles = collectValues(underTest.getTiles(TEST_USER_2)) + underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2) + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE_2) + } + + @Test + fun updatingWithDefaultsForTheSameUserAddsData() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true) + runCurrent() + + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" } + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile) + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile) + } + + @Test + fun updatingWithDefaultsForAnotherUserOverridesTile() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + val tiles = collectValues(underTest.getTiles(TEST_USER_2)) + underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2) + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE_2) + } + + private companion object { + + val TEST_COMPONENT = ComponentName("test.pkg", "test.cls") + + val TEST_USER_1 = UserHandle.of(1)!! + val TEST_TILE_1 = + Tile().apply { + label = "test_tile_1" + icon = Icon.createWithContentUri("file://test_1") + } + val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier) + val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) + + val TEST_USER_2 = UserHandle.of(2)!! + val TEST_TILE_2 = + Tile().apply { + label = "test_tile_2" + icon = Icon.createWithContentUri("file://test_2") + } + val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier) + val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt new file mode 100644 index 000000000000..eebb145ef384 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 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.impl.custom.domain.interactor + +import android.content.ComponentName +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import android.text.format.DateUtils +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.external.FakeCustomTileStatePersister +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.external.TileServiceManager +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository +import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class CustomTileInteractorTest : SysuiTestCase() { + + @Mock private lateinit var tileServiceManager: TileServiceManager + + private val testScope = TestScope() + + private val defaultsRepository = FakeCustomTileDefaultsRepository() + private val customTileStatePersister = FakeCustomTileStatePersister() + private val customTileRepository = + FakeCustomTileRepository( + TEST_TILE_SPEC, + customTileStatePersister, + testScope.testScheduler, + ) + + private lateinit var underTest: CustomTileInteractor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = + CustomTileInteractor( + TEST_USER, + defaultsRepository, + customTileRepository, + tileServiceManager, + testScope.backgroundScope, + testScope.testScheduler, + ) + } + + @Test + fun activeTileIsAvailableAfterRestored() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(true) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + + underTest.init() + + assertThat(underTest.tile).isEqualTo(TEST_TILE) + assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE) + } + + @Test + fun notActiveTileIsAvailableAfterUpdated() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(false) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + val tiles = collectValues(underTest.tiles) + val initJob = launch { underTest.init() } + + underTest.updateTile(TEST_TILE) + runCurrent() + initJob.join() + + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE) + } + + @Test + fun notActiveTileIsAvailableAfterDefaultsUpdated() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(false) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + val tiles = collectValues(underTest.tiles) + val initJob = launch { underTest.init() } + + defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS) + defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT) + runCurrent() + initJob.join() + + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE) + } + + @Test(expected = IllegalStateException::class) + fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile } + + @Test + fun initSuspendsForActiveTileNotRestoredAndNotUpdated() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(true) + val tiles = collectValues(underTest.tiles) + + val initJob = backgroundScope.launch { underTest.init() } + advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS) + + // Is still suspended + assertThat(initJob.isActive).isTrue() + assertThat(tiles()).isEmpty() + } + + @Test + fun initSuspendedForNotActiveTileWithoutUpdates() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(false) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + val tiles = collectValues(underTest.tiles) + + val initJob = backgroundScope.launch { underTest.init() } + advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS) + + // Is still suspended + assertThat(initJob.isActive).isTrue() + assertThat(tiles()).isEmpty() + } + + private companion object { + + val TEST_COMPONENT = ComponentName("test.pkg", "test.cls") + val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT) + val TEST_USER = UserHandle.of(1)!! + val TEST_TILE = + Tile().apply { + label = "test_tile_1" + icon = Icon.createWithContentUri("file://test_1") + } + val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt index fab290da2953..7b2ac90b9766 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain -import android.graphics.drawable.Drawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -26,7 +25,6 @@ import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTile import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import org.junit.Test @@ -37,7 +35,7 @@ import org.junit.runner.RunWith class FlashlightMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val qsTileConfig = kosmos.qsFlashlightTileConfig - private val mapper by lazy { FlashlightMapper(context) } + private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) } @Test fun mapsDisabledDataToInactiveState() { @@ -58,12 +56,7 @@ class FlashlightMapperTest : SysuiTestCase() { @Test fun mapsEnabledDataToOnIconState() { - val fakeDrawable = mock<Drawable>() - context.orCreateTestableResources.addOverride( - R.drawable.qs_flashlight_icon_on, - fakeDrawable - ) - val expectedIcon = Icon.Loaded(fakeDrawable, null) + val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null) val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true)) @@ -73,12 +66,7 @@ class FlashlightMapperTest : SysuiTestCase() { @Test fun mapsDisabledDataToOffIconState() { - val fakeDrawable = mock<Drawable>() - context.orCreateTestableResources.addOverride( - R.drawable.qs_flashlight_icon_off, - fakeDrawable - ) - val expectedIcon = Icon.Loaded(fakeDrawable, null) + val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null) val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt index 820c0564c1a7..8791877f8863 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles.impl.location.domain -import android.graphics.drawable.Drawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -26,7 +25,6 @@ import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileMode import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth import junit.framework.Assert import org.junit.Test @@ -37,7 +35,8 @@ import org.junit.runner.RunWith class LocationTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val qsTileConfig = kosmos.qsLocationTileConfig - private val mapper by lazy { LocationTileMapper(context) } + + private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) } @Test fun mapsDisabledDataToInactiveState() { @@ -57,9 +56,7 @@ class LocationTileMapperTest : SysuiTestCase() { @Test fun mapsEnabledDataToOnIconState() { - val fakeDrawable = mock<Drawable>() - context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_on, fakeDrawable) - val expectedIcon = Icon.Loaded(fakeDrawable, null) + val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null) val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) @@ -69,9 +66,7 @@ class LocationTileMapperTest : SysuiTestCase() { @Test fun mapsDisabledDataToOffIconState() { - val fakeDrawable = mock<Drawable>() - context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_off, fakeDrawable) - val expectedIcon = Icon.Loaded(fakeDrawable, null) + val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null) val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt index 5eca8caa7d15..5eca8caa7d15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt index 3a0ebdbd6a17..3a0ebdbd6a17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt index 22fb152aee44..22fb152aee44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 42e27ba12f42..42e27ba12f42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index c3294ff2e26c..18b7168098ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags @@ -61,7 +62,6 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -273,6 +273,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } @Test + fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(SceneKey.Lockscreen) } + + @Test fun clickLockButtonAndEnterCorrectPin_unlocksDevice() = testScope.runTest { emulateUserDrivenTransition(SceneKey.Bouncer) @@ -336,7 +339,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - assertTrue(deviceEntryInteractor.canSwipeToEnter.value) + assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue() assertCurrentScene(SceneKey.Lockscreen) // Emulate a user swipe to dismiss the lockscreen. @@ -775,11 +778,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private suspend fun TestScope.dismissIme( showImeBeforeDismissing: Boolean = true, ) { - bouncerViewModel.authMethodViewModel.value?.apply { + (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let { if (showImeBeforeDismissing) { - onImeVisibilityChanged(true) + it.onImeVisibilityChanged(true) } - onImeVisibilityChanged(false) + it.onImeVisibilityChanged(false) runCurrent() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index ddeb05b39e53..ddeb05b39e53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt index 7ae501d05fcd..7ae501d05fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 7f4bbbe36768..7f4bbbe36768 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index 8be4eeb7be7a..8be4eeb7be7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index c4ec56c906c3..3cb97e369a67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -145,6 +145,18 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + fun startsInLockscreenScene() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState() + + underTest.start() + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test fun switchToLockscreenWhenDeviceLocks() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) @@ -467,7 +479,7 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() runCurrent() - bouncerInteractor.onImeHidden() + bouncerInteractor.onImeHiddenByUser() runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index c89cd9e0c1f1..c89cd9e0c1f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 77ddf15c41ad..77ddf15c41ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index e2640af136a7..e2640af136a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt index cb83e7c7adbc..cb83e7c7adbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt index e09385934991..e09385934991 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt index 886c61ae29dd..886c61ae29dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt index 0b5aea7d8683..0b5aea7d8683 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java index c669c6f6fb1c..c669c6f6fb1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt index 638925d0a705..638925d0a705 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 5f4d7bf6f371..5f4d7bf6f371 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 75d1869adc7c..75d1869adc7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt index a80238167b85..a80238167b85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index 28d632d9fcea..28d632d9fcea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 106b54891948..106b54891948 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt index d06a6e26b4ce..d06a6e26b4ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt index ce00250467f6..ce00250467f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index cf20ba87e8c2..cf20ba87e8c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt index 7fbbfc77300e..7fbbfc77300e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index 791a028eef4e..791a028eef4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 9c08f5ef4cfe..355e75d0716b 100644 --- a/packages/SystemUI/res/drawable/notification_material_bg.xml +++ b/packages/SystemUI/res/drawable/notification_material_bg.xml @@ -18,7 +18,7 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="?android:attr/colorControlHighlight"> - <item android:id="@+id/notification_background_color_layer"> + <item> <shape> <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> </shape> diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index af29cada2657..50241cdca8b5 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -111,107 +111,57 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle" - app:layout_constraintBottom_toTopOf="@+id/see_all_text" /> + app:layout_constraintBottom_toTopOf="@+id/see_all_button" /> - <androidx.constraintlayout.widget.Group - android:id="@+id/see_all_layout_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ic_arrow,see_all_text" /> - - <View - android:id="@+id/see_all_clickable_row" + <Button + android:id="@+id/see_all_button" + style="@style/BluetoothTileDialog.Device" + android:paddingEnd="0dp" + android:paddingStart="20dp" + android:background="@drawable/bluetooth_tile_dialog_bg_off" android:layout_width="0dp" - android:layout_height="0dp" + android:layout_height="64dp" android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/device_list" - app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" /> - - <ImageView - android:id="@+id/ic_arrow" - android:layout_marginStart="36dp" - android:layout_width="24dp" - android:layout_height="24dp" - android:importantForAccessibility="no" - android:gravity="center_vertical" - android:src="@drawable/ic_arrow_forward" - app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/see_all_text" - app:layout_constraintTop_toBottomOf="@id/device_list" /> - - <TextView - android:id="@+id/see_all_text" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="64dp" - android:maxLines="1" - android:ellipsize="end" - android:gravity="center_vertical" - android:importantForAccessibility="no" - android:clickable="false" - android:layout_marginStart="0dp" - android:paddingStart="20dp" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button" + android:drawableStart="@drawable/ic_arrow_forward" + android:drawablePadding="20dp" + android:drawableTint="?android:attr/textColorPrimary" android:text="@string/see_all_bluetooth_devices" android:textSize="14sp" android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" - app:layout_constraintStart_toEndOf="@+id/ic_arrow" - app:layout_constraintTop_toBottomOf="@id/device_list" - app:layout_constraintEnd_toEndOf="parent" /> - - <androidx.constraintlayout.widget.Group - android:id="@+id/pair_new_device_layout_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + android:textDirection="locale" + android:textAlignment="viewStart" + android:maxLines="1" + android:ellipsize="end" + android:visibility="gone" /> - <View - android:id="@+id/pair_new_device_clickable_row" + <Button + android:id="@+id/pair_new_device_button" + style="@style/BluetoothTileDialog.Device" + android:paddingEnd="0dp" + android:paddingStart="20dp" + android:background="@drawable/bluetooth_tile_dialog_bg_off" android:layout_width="0dp" - android:layout_height="0dp" + android:layout_height="64dp" android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@+id/see_all_text" - app:layout_constraintBottom_toTopOf="@+id/done_button" /> - - <ImageView - android:id="@+id/ic_add" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginStart="36dp" - android:gravity="center_vertical" - android:importantForAccessibility="no" - android:src="@drawable/ic_add" - app:layout_constraintBottom_toTopOf="@id/done_button" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" - app:layout_constraintTop_toBottomOf="@id/see_all_text" - android:tint="?android:attr/textColorPrimary" /> - - <TextView - android:id="@+id/pair_new_device_text" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="64dp" - android:maxLines="1" - android:ellipsize="end" - android:gravity="center_vertical" - android:importantForAccessibility="no" - android:clickable="false" - android:layout_marginStart="0dp" - android:paddingStart="20dp" + app:layout_constraintTop_toBottomOf="@+id/see_all_button" + app:layout_constraintBottom_toTopOf="@+id/done_button" + android:drawableStart="@drawable/ic_add" + android:drawablePadding="20dp" + android:drawableTint="?android:attr/textColorPrimary" android:text="@string/pair_new_bluetooth_devices" android:textSize="14sp" android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintStart_toEndOf="@+id/ic_add" - app:layout_constraintTop_toBottomOf="@id/see_all_text" - app:layout_constraintEnd_toEndOf="parent" /> + android:textDirection="locale" + android:textAlignment="viewStart" + android:maxLines="1" + android:ellipsize="end" + android:visibility="gone" /> <Button android:id="@+id/done_button" @@ -227,7 +177,7 @@ android:maxLines="1" android:text="@string/inline_done_button" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" + app:layout_constraintTop_toBottomOf="@id/pair_new_device_button" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.core.widget.NestedScrollView> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index d9385c7d24c4..bcc3c83b4560 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -104,4 +104,7 @@ <!-- Internet Dialog --> <color name="connected_network_primary_color">@color/material_dynamic_primary80</color> <color name="connected_network_secondary_color">@color/material_dynamic_secondary80</color> + + <!-- Keyboard shortcut helper dialog --> + <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index e124aa0eda37..5f6a39a91b8b 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -119,7 +119,7 @@ <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> - <color name="ksh_key_item_color">?androidprv:attr/materialColorOnSurfaceVariant</color> + <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_light</color> <color name="ksh_key_item_background">?androidprv:attr/materialColorSurfaceContainerHighest</color> <color name="instant_apps_color">#ff4d5a64</color> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index b704f3c89330..1edb551eb944 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -71,7 +71,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { private final DisplayTracker mDisplayTracker; private final AccessibilityLogger mA11yLogger; - private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; + private MagnificationConnectionImpl mMagnificationConnectionImpl; private SysUiState mSysUiState; @VisibleForTesting @@ -220,7 +220,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { } @MainThread - void setScale(int displayId, float scale) { + void setScaleForWindowMagnification(int displayId, float scale) { final WindowMagnificationController windowMagnificationController = mMagnificationControllerSupplier.get(displayId); if (windowMagnificationController != null) { @@ -321,37 +321,37 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() { @Override public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame); + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame); } } @Override public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds); + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds); } } @Override public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onPerformScaleAction( + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onPerformScaleAction( displayId, scale, updatePersistence); } } @Override public void onAccessibilityActionPerformed(int displayId) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId); + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId); } } @Override public void onMove(int displayId) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onMove(displayId); + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onMove(displayId); } } @@ -394,8 +394,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @Override public void onMagnifierScale(int displayId, float scale, boolean updatePersistence) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onPerformScaleAction( + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onPerformScaleAction( displayId, scale, updatePersistence); } mA11yLogger.logThrottled( @@ -454,8 +454,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { if (magnificationSettingsController != null) { magnificationSettingsController.closeMagnificationSettings(); } - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode); + if (mMagnificationConnectionImpl != null) { + mMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode); } } } @@ -500,12 +500,12 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { } private void setWindowMagnificationConnection() { - if (mWindowMagnificationConnectionImpl == null) { - mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this, + if (mMagnificationConnectionImpl == null) { + mMagnificationConnectionImpl = new MagnificationConnectionImpl(this, mHandler); } mAccessibilityManager.setWindowMagnificationConnection( - mWindowMagnificationConnectionImpl); + mMagnificationConnectionImpl); } private void clearWindowMagnificationConnection() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java index 5666851f560f..5f0d496dd5d1 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java @@ -32,7 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main; * * @see IWindowMagnificationConnection */ -class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.Stub { +class MagnificationConnectionImpl extends IWindowMagnificationConnection.Stub { private static final String TAG = "WindowMagnificationConnectionImpl"; @@ -40,7 +40,7 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S private final Magnification mMagnification; private final Handler mHandler; - WindowMagnificationConnectionImpl(@NonNull Magnification magnification, + MagnificationConnectionImpl(@NonNull Magnification magnification, @Main Handler mainHandler) { mMagnification = magnification; mHandler = mainHandler; @@ -57,8 +57,8 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S } @Override - public void setScale(int displayId, float scale) { - mHandler.post(() -> mMagnification.setScale(displayId, scale)); + public void setScaleForWindowMagnification(int displayId, float scale) { + mHandler.post(() -> mMagnification.setScaleForWindowMagnification(displayId, scale)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 1ac4163649a4..ab23564a1df4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -30,6 +30,7 @@ import android.content.Context; import android.graphics.PixelFormat; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -168,7 +169,7 @@ public class AuthContainerView extends LinearLayout // HAT received from LockSettingsService when credential is verified. @Nullable private byte[] mCredentialAttestation; - // TODO(b/287311775): remove when legacy prompt is replaced + // TODO(b/313469218): remove when legacy prompt is replaced @Deprecated static class Config { Context mContext; @@ -220,6 +221,9 @@ public class AuthContainerView extends LinearLayout mHandler.postDelayed(() -> { addCredentialView(false /* animatePanel */, true /* animateContents */); }, mConfig.mSkipAnimation ? 0 : ANIMATE_CREDENTIAL_START_DELAY_MS); + + // TODO(b/313469218): Remove Config + mConfig.mPromptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt index 11a5d8b578df..3defec5ca48d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -485,8 +485,7 @@ constructor( ): Int = if (isPendingConfirmation) { when (sensorType) { - FingerprintSensorType.POWER_BUTTON -> - R.string.security_settings_sfps_enroll_find_sensor_message + FingerprintSensorType.POWER_BUTTON -> -1 else -> R.string.fingerprint_dialog_authenticated_confirmation } } else if (isAuthenticating || isAuthenticated) { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index b598631c3b57..7c46339ec103 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -105,9 +105,9 @@ constructor( val isUserSwitcherVisible: Boolean get() = repository.isUserSwitcherVisible - private val _onImeHidden = MutableSharedFlow<Unit>() - /** Provide the onImeHidden events from the bouncer */ - val onImeHidden: SharedFlow<Unit> = _onImeHidden + private val _onImeHiddenByUser = MutableSharedFlow<Unit>() + /** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */ + val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser init { if (flags.isEnabled()) { @@ -230,9 +230,9 @@ constructor( repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod())) } - /** Notifies the interactor that the input method editor has been hidden. */ - suspend fun onImeHidden() { - _onImeHidden.emit(Unit) + /** Notifies that the input method editor (software keyboard) has been hidden by the user. */ + suspend fun onImeHiddenByUser() { + _onImeHiddenByUser.emit(Unit) } private fun promptMessage(authMethod: AuthenticationMethodModel): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 80248744c25a..e379dab918ef 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -46,9 +46,6 @@ sealed class AuthMethodBouncerViewModel( */ val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow() - /** Whether the input method editor (for example, the software keyboard) is visible. */ - private var isImeVisible: Boolean = false - /** The authentication method that corresponds to this view model. */ abstract val authenticationMethod: AuthenticationMethodModel @@ -68,7 +65,7 @@ sealed class AuthMethodBouncerViewModel( /** * Notifies that the UI has been hidden from the user (after any transitions have completed). */ - fun onHidden() { + open fun onHidden() { clearInput() interactor.resetMessage() } @@ -79,18 +76,6 @@ sealed class AuthMethodBouncerViewModel( } /** - * Notifies that the input method editor (for example, the software keyboard) has been shown or - * hidden. - */ - suspend fun onImeVisibilityChanged(isVisible: Boolean) { - if (isImeVisible && !isVisible) { - interactor.onImeHidden() - } - - isImeVisible = isVisible - } - - /** * Notifies that the failure animation has been shown. This should be called to consume a `true` * value in [animateFailure]. */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index a15698e1f90c..45d181285df7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -21,8 +21,11 @@ import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn /** Holds UI state and handles user input for the password bouncer UI. */ class PasswordBouncerViewModel( @@ -45,6 +48,32 @@ class PasswordBouncerViewModel( override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message + /** Whether the input method editor (for example, the software keyboard) is visible. */ + private var isImeVisible: Boolean = false + + /** Whether the text field element currently has focus. */ + private val isTextFieldFocused = MutableStateFlow(false) + + /** Whether the UI should request focus on the text field element. */ + val isTextFieldFocusRequested = + combine( + interactor.isThrottled, + isTextFieldFocused, + ) { isThrottled, hasFocus -> + !isThrottled && !hasFocus + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = !interactor.isThrottled.value && !isTextFieldFocused.value, + ) + + override fun onHidden() { + super.onHidden() + isImeVisible = false + isTextFieldFocused.value = false + } + override fun clearInput() { _password.value = "" } @@ -72,4 +101,21 @@ class PasswordBouncerViewModel( tryAuthenticate() } } + + /** + * Notifies that the input method editor (for example, the software keyboard) has been shown or + * hidden. + */ + suspend fun onImeVisibilityChanged(isVisible: Boolean) { + if (isImeVisible && !isVisible && !interactor.isThrottled.value) { + interactor.onImeHiddenByUser() + } + + isImeVisible = isVisible + } + + /** Notifies that the password text field has gained or lost focus. */ + fun onTextFieldFocusChanged(isFocused: Boolean) { + isTextFieldFocused.value = isFocused + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 715fb17c7c2d..4cddb9ccffdb 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -103,8 +103,11 @@ constructor( initialValue = false, ) - // Authenticated by a TrustAgent like trusted device, location, etc or by face auth. - private val passivelyAuthenticated = + /** + * Whether the user is currently authenticated by a TrustAgent like trusted device, location, + * etc., or by face auth. + */ + private val isPassivelyAuthenticated = merge( trustRepository.isCurrentUserTrusted, deviceEntryFaceAuthRepository.isAuthenticated, @@ -117,25 +120,31 @@ constructor( * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been * dismissed. * + * A value of `null` is meaningless and is used as placeholder while the actual value is still + * being loaded in the background. + * * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other * UI. */ - val canSwipeToEnter = + val canSwipeToEnter: StateFlow<Boolean?> = combine( // This is true when the user has chosen to show the lockscreen but has not made it // secure. authenticationInteractor.authenticationMethod.map { it == AuthenticationMethodModel.None && repository.isLockscreenEnabled() }, - passivelyAuthenticated, + isPassivelyAuthenticated, isDeviceEntered - ) { isSwipeAuthMethod, passivelyAuthenticated, isDeviceEntered -> - (isSwipeAuthMethod || passivelyAuthenticated) && !isDeviceEntered + ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered -> + (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, - initialValue = false, + // Starts as null to prevent downstream collectors from falsely assuming that the + // user can or cannot swipe to enter the device while the real value is being loaded + // from upstream data sources. + initialValue = null, ) /** diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 2a0d6a8a77d9..0c24752c5345 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -545,28 +545,12 @@ object Flags { unreleasedFlag("clipboard_shared_transitions", teamfood = true) /** - * Whether the scene container (Flexiglass) is enabled. Note that [SCENE_CONTAINER] should be - * checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can remove - * unused code from our APK at compile time. + * Whether the scene container (Flexiglass) is enabled. Note that SceneContainerFlags#isEnabled + * should be checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can + * remove unused code from our APK at compile time. */ // TODO(b/283300105): Tracking Bug @JvmField val SCENE_CONTAINER_ENABLED = false - @Deprecated( - message = """ - Do not use this flag directly. Please use - [com.android.systemui.scene.shared.flag.SceneContainerFlags#isEnabled]. - - (Not really deprecated but using this as a simple way to bring attention to the above). - """, - replaceWith = ReplaceWith( - "com.android.systemui.scene.shared.flag.SceneContainerFlags#isEnabled", - ), - level = DeprecationLevel.WARNING, - ) - @JvmField val SCENE_CONTAINER = resourceBooleanFlag( - R.bool.config_sceneContainerFrameworkEnabled, - "scene_container", - ) // 1900 @JvmField val NOTE_TASKS = releasedFlag("keycode_flag") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index bd73d60cda29..62a0b0ebc08c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -131,13 +131,16 @@ constructor( when (toState) { KeyguardState.DREAMING -> TO_DREAMING_DURATION KeyguardState.AOD -> TO_AOD_DURATION + KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } } + companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds val TO_AOD_DURATION = 1300.milliseconds + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 152d2172ee4c..cbfd17ff7ae4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -380,6 +380,8 @@ constructor( KeyguardState.DREAMING -> TO_DREAMING_DURATION KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION KeyguardState.AOD -> TO_AOD_DURATION + KeyguardState.DOZING -> TO_DOZING_DURATION + KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -388,7 +390,9 @@ constructor( companion object { const val TAG = "FromLockscreenTransitionInteractor" private val DEFAULT_DURATION = 400.milliseconds + val TO_DOZING_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds + val TO_DREAMING_HOSTED_DURATION = 933.milliseconds val TO_OCCLUDED_DURATION = 450.milliseconds val TO_AOD_DURATION = 500.milliseconds val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 706aba3c0505..f7d1543e4650 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -72,6 +72,10 @@ constructor( val fromDreamingTransition: Flow<TransitionStep> = repository.transitions.filter { step -> step.from == DREAMING } + /** LOCKSCREEN->(any) transition information. */ + val fromLockscreenTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.from == LOCKSCREEN } + /** (any)->Lockscreen transition information */ val anyStateToLockscreenTransition: Flow<TransitionStep> = repository.transitions.filter { step -> step.to == LOCKSCREEN } @@ -113,9 +117,16 @@ constructor( val goneToDreamingLockscreenHostedTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING_LOCKSCREEN_HOSTED) + /** GONE->LOCKSCREEN transition information. */ + val goneToLockscreenTransition: Flow<TransitionStep> = repository.transition(GONE, LOCKSCREEN) + /** LOCKSCREEN->AOD transition information. */ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) + /** LOCKSCREEN->DOZING transition information. */ + val lockscreenToDozingTransition: Flow<TransitionStep> = + repository.transition(LOCKSCREEN, DOZING) + /** LOCKSCREEN->DREAMING transition information. */ val lockscreenToDreamingTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, DREAMING) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 99025acef70d..abd79ab793d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -30,9 +30,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.animation.Interpolators import com.android.settingslib.Utils -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.common.shared.model.Icon @@ -40,6 +38,7 @@ import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd import kotlinx.coroutines.flow.Flow @@ -48,9 +47,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -/** - * This is only for a SINGLE Quick affordance - */ +/** This is only for a SINGLE Quick affordance */ object KeyguardQuickAffordanceViewBinder { private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L @@ -135,28 +132,12 @@ object KeyguardQuickAffordanceViewBinder { vibratorHelper: VibratorHelper?, ) { if (!viewModel.isVisible) { - view.alpha = 1f - view - .animate() - .alpha(0f) - .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) - .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS) - .withEndAction { view.isInvisible = true } - .start() + view.isInvisible = true return } if (!view.isVisible) { view.isVisible = true - if (viewModel.animateReveal) { - view.alpha = 0f - view - .animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) - .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS) - .start() - } } IconViewBinder.bind(viewModel.icon, view) @@ -216,13 +197,14 @@ object KeyguardQuickAffordanceViewBinder { view.isClickable = viewModel.isClickable if (viewModel.isClickable) { if (viewModel.useLongPress) { - val onTouchListener = KeyguardQuickAffordanceOnTouchListener( - view, - viewModel, - messageDisplayer, - vibratorHelper, - falsingManager, - ) + val onTouchListener = + KeyguardQuickAffordanceOnTouchListener( + view, + viewModel, + messageDisplayer, + vibratorHelper, + falsingManager, + ) view.setOnTouchListener(onTouchListener) view.setOnClickListener { messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short) @@ -241,9 +223,7 @@ object KeyguardQuickAffordanceViewBinder { KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds shakeAnimator.interpolator = CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) - shakeAnimator.doOnEnd { - view.translationX = 0f - } + shakeAnimator.doOnEnd { view.translationX = 0f } shakeAnimator.start() vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) @@ -268,18 +248,18 @@ object KeyguardQuickAffordanceViewBinder { alphaFlow: Flow<Float>, ) { combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha -> - if (isDimmed) DIM_ALPHA else alpha - } + if (isDimmed) DIM_ALPHA else alpha + } .collect { view.alpha = it } } private fun loadFromResources(view: View): ConfigurationBasedDimensions { return ConfigurationBasedDimensions( buttonSizePx = - Size( - view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), - view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), - ), + Size( + view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), + view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), + ), ) } @@ -337,11 +317,9 @@ object KeyguardQuickAffordanceViewBinder { } override fun onLongClickUseDefaultHapticFeedback(view: View) = false - } private data class ConfigurationBasedDimensions( val buttonSizePx: Size, ) - -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index a2e930c49511..59c798bfca1e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -84,6 +84,7 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking /** Renders the preview of the lock screen. */ @@ -158,7 +159,6 @@ constructor( init { if (keyguardBottomAreaRefactor()) { - keyguardRootViewModel.enablePreviewMode() quickAffordancesCombinedViewModel.enablePreviewMode( initiallySelectedSlotId = bundle.getString( @@ -287,6 +287,10 @@ constructor( return } + if (smartSpaceView != null) { + parentView.removeView(smartSpaceView) + } + smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) val topPadding: Int = @@ -334,26 +338,27 @@ constructor( ), ) } - @OptIn(ExperimentalCoroutinesApi::class) private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { val keyguardRootView = KeyguardRootView(previewContext, null) - disposables.add( - KeyguardRootViewBinder.bind( - keyguardRootView, - keyguardRootViewModel, - configuration, - featureFlags, - occludingAppDeviceEntryMessageViewModel, - chipbarCoordinator, - screenOffAnimationController, - shadeInteractor, - null, // clock provider only needed for burn in - null, // jank monitor not required for preview mode - null, // device entry haptics not required for preview mode - null, // device entry haptics not required for preview mode + if (!keyguardBottomAreaRefactor()) { + disposables.add( + KeyguardRootViewBinder.bind( + keyguardRootView, + keyguardRootViewModel, + configuration, + featureFlags, + occludingAppDeviceEntryMessageViewModel, + chipbarCoordinator, + screenOffAnimationController, + shadeInteractor, + null, // clock provider only needed for burn in + null, // jank monitor not required for preview mode + null, // device entry haptics not required preview mode + null, // device entry haptics not required for preview mode + ) ) - ) + } rootView.addView( keyguardRootView, FrameLayout.LayoutParams( @@ -362,12 +367,13 @@ constructor( ), ) + setUpUdfps(previewContext, rootView) + disposables.add( PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) { if (keyguardBottomAreaRefactor()) { setupShortcuts(keyguardRootView) } - setUpUdfps(previewContext, rootView) if (!shouldHideClock) { setUpClock(previewContext, rootView) @@ -387,30 +393,30 @@ constructor( } private fun setupShortcuts(keyguardRootView: ConstraintLayout) { - keyguardRootView.findViewById<LaunchableImageView?>(R.id.start_button)?.let { + keyguardRootView.findViewById<LaunchableImageView?>(R.id.start_button)?.let { imageView -> shortcutsBindings.add( KeyguardQuickAffordanceViewBinder.bind( - it, - quickAffordancesCombinedViewModel.startButton, - keyguardRootViewModel.alpha, - falsingManager, - vibratorHelper, - ) { - indicationController.showTransientIndication(it) + view = imageView, + viewModel = quickAffordancesCombinedViewModel.startButton, + alpha = flowOf(1f), + falsingManager = falsingManager, + vibratorHelper = vibratorHelper, + ) { message -> + indicationController.showTransientIndication(message) } ) } - keyguardRootView.findViewById<LaunchableImageView?>(R.id.end_button)?.let { + keyguardRootView.findViewById<LaunchableImageView?>(R.id.end_button)?.let { imageView -> shortcutsBindings.add( KeyguardQuickAffordanceViewBinder.bind( - it, - quickAffordancesCombinedViewModel.endButton, - keyguardRootViewModel.alpha, - falsingManager, - vibratorHelper, - ) { - indicationController.showTransientIndication(it) + view = imageView, + viewModel = quickAffordancesCombinedViewModel.endButton, + alpha = flowOf(1f), + falsingManager = falsingManager, + vibratorHelper = vibratorHelper, + ) { message -> + indicationController.showTransientIndication(message) } ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt index 55df46679f6d..cd46d6cf2188 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt @@ -61,7 +61,7 @@ constructor( KeyguardQuickAffordanceViewBinder.bind( constraintLayout.requireViewById(R.id.start_button), keyguardQuickAffordancesCombinedViewModel.startButton, - keyguardRootViewModel.alpha, + keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, ) { @@ -71,7 +71,7 @@ constructor( KeyguardQuickAffordanceViewBinder.bind( constraintLayout.requireViewById(R.id.end_button), keyguardQuickAffordancesCombinedViewModel.endButton, - keyguardRootViewModel.alpha, + keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 0f6a966aad2e..2a68f26d3ae7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -25,14 +25,12 @@ import androidx.constraintlayout.widget.ConstraintSet.LEFT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.RIGHT import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject @@ -61,7 +59,7 @@ constructor( KeyguardQuickAffordanceViewBinder.bind( constraintLayout.requireViewById(R.id.start_button), keyguardQuickAffordancesCombinedViewModel.startButton, - keyguardRootViewModel.alpha, + keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, ) { @@ -71,7 +69,7 @@ constructor( KeyguardQuickAffordanceViewBinder.bind( constraintLayout.requireViewById(R.id.end_button), keyguardQuickAffordancesCombinedViewModel.endButton, - keyguardRootViewModel.alpha, + keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index 14de01b41867..1864437a7d11 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -55,6 +55,14 @@ constructor( onStep = { 1f }, ) + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 167.milliseconds, + startTime = 67.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) + val deviceEntryBackgroundViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> if (isUdfps) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt index 27fb8a3d2473..a728a2810916 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -22,6 +22,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -41,6 +42,13 @@ constructor( transitionFlow = interactor.dozingToLockscreenTransition, ) + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 150.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..58235ae02abe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class DreamingHostedToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.dreamingLockscreenHostedToLockscreenTransition + ) + + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index a3b8b85fc53d..f943bdfa7550 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -96,6 +96,14 @@ constructor( onStep = { it }, ) + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + startTime = 233.milliseconds, + duration = 250.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) + val deviceEntryBackgroundViewAlpha = deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> if (isUdfps) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..5804a205445c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class GoneToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.goneToLockscreenTransition + ) + + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index 02ea5508f34f..188be244be4a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge @OptIn(ExperimentalCoroutinesApi::class) class KeyguardQuickAffordancesCombinedViewModel @@ -39,6 +41,22 @@ class KeyguardQuickAffordancesCombinedViewModel constructor( private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, private val keyguardInteractor: KeyguardInteractor, + shadeInteractor: ShadeInteractor, + aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, + dreamingHostedToLockscreenTransitionViewModel: DreamingHostedToLockscreenTransitionViewModel, + dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel, + occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel, + primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, + lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, + lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel, + lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel, + lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, + lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, + lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, + lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel, ) { data class PreviewMode( @@ -60,6 +78,39 @@ constructor( private val selectedPreviewSlotId = MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START) + /** alpha while fading the quick affordances out */ + private val fadeInAlpha: Flow<Float> = + merge( + aodToLockscreenTransitionViewModel.shortcutsAlpha, + dozingToLockscreenTransitionViewModel.shortcutsAlpha, + dreamingHostedToLockscreenTransitionViewModel.shortcutsAlpha, + dreamingToLockscreenTransitionViewModel.shortcutsAlpha, + goneToLockscreenTransitionViewModel.shortcutsAlpha, + occludedToLockscreenTransitionViewModel.shortcutsAlpha, + offToLockscreenTransitionViewModel.shortcutsAlpha, + primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha, + ) + + /** alpha while fading the quick affordances in */ + private val fadeOutAlpha: Flow<Float> = + merge( + lockscreenToAodTransitionViewModel.shortcutsAlpha, + lockscreenToDozingTransitionViewModel.shortcutsAlpha, + lockscreenToDreamingHostedTransitionViewModel.shortcutsAlpha, + lockscreenToDreamingTransitionViewModel.shortcutsAlpha, + lockscreenToGoneTransitionViewModel.shortcutsAlpha, + lockscreenToOccludedTransitionViewModel.shortcutsAlpha, + lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha, + shadeInteractor.qsExpansion.map { 1 - it }, + ) + + /** The source of truth of alpha for all of the quick affordances on lockscreen */ + val transitionAlpha: Flow<Float> = + merge( + fadeInAlpha, + fadeOutAlpha, + ) + /** * Whether quick affordances are "opaque enough" to be considered visible to and interactive by * the user. If they are not interactive, user input should not be allowed on them. @@ -73,7 +124,7 @@ constructor( * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987. */ private val areQuickAffordancesFullyOpaque: Flow<Boolean> = - keyguardInteractor.keyguardAlpha + transitionAlpha .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD } .distinctUntilChanged() @@ -89,7 +140,7 @@ constructor( * Notifies that a slot with the given ID has been selected in the preview experience that is * rendering in the wallpaper picker. This is ignored for the real lock screen experience. * - * @see [KeyguardRootViewModel.enablePreviewMode] + * @see [enablePreviewMode] */ fun onPreviewSlotSelected(slotId: String) { selectedPreviewSlotId.value = slotId diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 524fa1ede90a..f63afebb60ab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -47,13 +47,11 @@ import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart @@ -74,16 +72,6 @@ constructor( private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, screenOffAnimationController: ScreenOffAnimationController, ) { - - data class PreviewMode(val isInPreviewMode: Boolean = false) - - /** - * Whether this view-model instance is powering the preview experience that renders exclusively - * in the wallpaper picker application. This should _always_ be `false` for the real lock screen - * experience. - */ - private val previewMode = MutableStateFlow(PreviewMode()) - var clockControllerProvider: Provider<ClockController>? = null /** System insets that keyguard needs to stay out of */ @@ -103,14 +91,7 @@ constructor( keyguardInteractor.notificationContainerBounds /** An observable for the alpha level for the entire keyguard root view. */ - val alpha: Flow<Float> = - previewMode.flatMapLatest { - if (it.isInPreviewMode) { - flowOf(1f) - } else { - keyguardInteractor.keyguardAlpha.distinctUntilChanged() - } - } + val alpha: Flow<Float> = keyguardInteractor.keyguardAlpha.distinctUntilChanged() private fun burnIn(): Flow<BurnInModel> { val dozingAmount: Flow<Float> = @@ -147,55 +128,29 @@ constructor( val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha /** For elements that appear and move during the animation -> AOD */ - val burnInLayerAlpha: Flow<Float> = - previewMode.flatMapLatest { - if (it.isInPreviewMode) { - flowOf(1f) - } else { - goneToAodTransitionViewModel.enterFromTopAnimationAlpha - } - } + val burnInLayerAlpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha val translationY: Flow<Float> = - previewMode.flatMapLatest { - if (it.isInPreviewMode) { - flowOf(0f) - } else { - keyguardInteractor.configurationChange.flatMapLatest { _ -> - val enterFromTopAmount = - context.resources.getDimensionPixelSize( - R.dimen.keyguard_enter_from_top_translation_y - ) - combine( - keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, - burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) }, - goneToAodTransitionViewModel - .enterFromTopTranslationY(enterFromTopAmount) - .onStart { emit(0f) }, - ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY -> - // All 3 values need to be combined for a smooth translation - keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY - } - } + keyguardInteractor.configurationChange.flatMapLatest { _ -> + val enterFromTopAmount = + context.resources.getDimensionPixelSize( + R.dimen.keyguard_enter_from_top_translation_y + ) + combine( + keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, + burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) }, + goneToAodTransitionViewModel.enterFromTopTranslationY(enterFromTopAmount).onStart { + emit(0f) + }, + ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY -> + // All 3 values need to be combined for a smooth translation + keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY } } - val translationX: Flow<Float> = - previewMode.flatMapLatest { - if (it.isInPreviewMode) { - flowOf(0f) - } else { - burnIn().map { it.translationX.toFloat() } - } - } + val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() } - val scale: Flow<Pair<Float, Boolean>> = - previewMode.flatMapLatest { previewMode -> - burnIn().map { - val scale = if (previewMode.isInPreviewMode) 1f else it.scale - Pair(scale, it.scaleClockOnly) - } - } + val scale: Flow<Pair<Float, Boolean>> = burnIn().map { Pair(it.scale, it.scaleClockOnly) } /** Is the notification icon container visible? */ val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> = @@ -238,20 +193,7 @@ constructor( } .distinctUntilChanged() - /** - * Puts this view-model in "preview mode", which means it's being used for UI that is rendering - * the lock screen preview in wallpaper picker / settings and not the real experience on the - * lock screen. - */ - fun enablePreviewMode() { - previewMode.value = PreviewMode(true) - } - fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { - // Notifications should not be visible in preview mode - if (previewMode.value.isInPreviewMode) { - return - } keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom)) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt index 2bf12e8e33b2..8e8fd75cc1c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -57,6 +57,15 @@ constructor( onFinish = { 0f }, ), ) + + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + onCancel = { 1f }, + ) + override val deviceEntryParentViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { isUdfpsEnrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt new file mode 100644 index 000000000000..263ed11503ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class LockscreenToDozingTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_DOZING_DURATION, + transitionFlow = interactor.lockscreenToDozingTransition + ) + + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + onCancel = { 1f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt new file mode 100644 index 000000000000..17015056bda0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class LockscreenToDreamingHostedTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_DREAMING_HOSTED_DURATION, + transitionFlow = interactor.lockscreenToDreamingLockscreenHostedTransition + ) + + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + onCancel = { 1f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index 52296137a3d6..401c0ff76c29 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -62,6 +62,14 @@ constructor( onStep = { 1f - it }, ) + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + onCancel = { 1f }, + ) + override val deviceEntryParentViewAlpha: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt index 59e5aa845051..cfb4bf59c8a8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -43,6 +44,14 @@ constructor( transitionFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE), ) + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + onCancel = { 1f }, + ) + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index d49bc4994b0f..a6136f95d0f6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -50,6 +50,14 @@ constructor( onStep = { 1f - it }, ) + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + onCancel = { 1f }, + ) + /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { return transitionAnimation.createFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index f04b67a1d4d4..07dd4ef49c5d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -26,6 +26,7 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map /** * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to @@ -46,6 +47,11 @@ constructor( interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER), ) + val shortcutsAlpha: Flow<Float> = + interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER).map { + 1 - it.value + } + override val deviceEntryParentViewAlpha: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 0bdc85d05106..58be0934beca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -58,6 +58,13 @@ constructor( ) } + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) + /** Lockscreen views alpha */ val lockscreenAlpha: Flow<Float> = transitionAnimation.createFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..c3bc799435a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class OffToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = 250.milliseconds, + transitionFlow = interactor.offToLockscreenTransition + ) + + val shortcutsAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onCancel = { 0f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index 3cf793ab9dc8..7ef8374023fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map /** * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to @@ -57,6 +58,11 @@ constructor( } } + val shortcutsAlpha: Flow<Float> = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN).map { + it.value + } + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index eba1c25d0e74..388418487725 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -36,11 +36,11 @@ import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.tileimpl.HeightOverrideable; import com.android.systemui.tuner.TunerService; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.concurrent.Executor; import javax.inject.Inject; @@ -64,6 +64,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private static final String TAG = "QSAnimator"; + private static final int ANIMATORS_UPDATE_DELAY_MS = 100; private static final float EXPANDED_TILE_DELAY = .86f; //Non first page delays private static final float QS_TILE_LABEL_FADE_OUT_START = 0.15f; @@ -133,7 +134,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private int mLastQQSTileHeight; private float mLastPosition; private final QSHost mHost; - private final Executor mExecutor; + private final DelayableExecutor mExecutor; private boolean mShowCollapsedOnKeyguard; private int mQQSTop; @@ -144,7 +145,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener public QSAnimator(@RootView View rootView, QuickQSPanel quickPanel, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, QSHost qsTileHost, - @Main Executor executor, TunerService tunerService, + @Main DelayableExecutor executor, TunerService tunerService, QSExpansionPathInterpolator qsExpansionPathInterpolator) { mQsRootView = rootView; mQuickQsPanel = quickPanel; @@ -753,7 +754,10 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener public void onTilesChanged() { // Give the QS panels a moment to generate their new tiles, then create all new animators // hooked up to the new views. - mExecutor.execute(mUpdateAnimators); + mExecutor.executeDelayed(mUpdateAnimators, ANIMATORS_UPDATE_DELAY_MS); + + // Also requests a lazy animators update in case the animation starts before the executor. + requestAnimatorUpdate(); } private final TouchAnimator.Listener mNonFirstPageListener = diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 11db69b69f13..6c930b1f3d17 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -255,6 +255,10 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr for (QSTile tile : tiles) { addTile(tile, collapsedView); } + } else { + for (QSPanelControllerBase.TileRecord record : mRecords) { + record.tile.addCallback(record.callback); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt index a321eef75a14..6f5dea32bd83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt @@ -18,17 +18,19 @@ package com.android.systemui.qs.external import android.content.ComponentName import android.content.Context +import android.content.SharedPreferences import android.service.quicksettings.Tile import android.util.Log import com.android.internal.annotations.VisibleForTesting +import javax.inject.Inject import org.json.JSONException import org.json.JSONObject -import javax.inject.Inject data class TileServiceKey(val componentName: ComponentName, val user: Int) { private val string = "${componentName.flattenToString()}:$user" override fun toString() = string } + private const val STATE = "state" private const val LABEL = "label" private const val SUBTITLE = "subtitle" @@ -44,12 +46,7 @@ private const val STATE_DESCRIPTION = "state_description" * It persists the state from a [Tile] necessary to present the view in the same state when * retrieved, with the exception of the icon. */ -class CustomTileStatePersister @Inject constructor(context: Context) { - companion object { - private const val FILE_NAME = "custom_tiles_state" - } - - private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) +interface CustomTileStatePersister { /** * Read the state from [SharedPreferences]. @@ -58,7 +55,31 @@ class CustomTileStatePersister @Inject constructor(context: Context) { * * Any fields that have not been saved will be set to `null` */ - fun readState(key: TileServiceKey): Tile? { + fun readState(key: TileServiceKey): Tile? + /** + * Persists the state into [SharedPreferences]. + * + * The implementation does not store fields that are `null` or icons. + */ + fun persistState(key: TileServiceKey, tile: Tile) + /** + * Removes the state for a given tile, user pair. + * + * Used when the tile is removed by the user. + */ + fun removeState(key: TileServiceKey) +} + +// TODO(b/299909989) Merge this class into into CustomTileRepository +class CustomTileStatePersisterImpl @Inject constructor(context: Context) : + CustomTileStatePersister { + companion object { + private const val FILE_NAME = "custom_tiles_state" + } + + private val sharedPreferences: SharedPreferences = context.getSharedPreferences(FILE_NAME, 0) + + override fun readState(key: TileServiceKey): Tile? { val state = sharedPreferences.getString(key.toString(), null) ?: return null return try { readTileFromString(state) @@ -68,23 +89,13 @@ class CustomTileStatePersister @Inject constructor(context: Context) { } } - /** - * Persists the state into [SharedPreferences]. - * - * The implementation does not store fields that are `null` or icons. - */ - fun persistState(key: TileServiceKey, tile: Tile) { + override fun persistState(key: TileServiceKey, tile: Tile) { val state = writeToString(tile) sharedPreferences.edit().putString(key.toString(), state).apply() } - /** - * Removes the state for a given tile, user pair. - * - * Used when the tile is removed by the user. - */ - fun removeState(key: TileServiceKey) { + override fun removeState(key: TileServiceKey) { sharedPreferences.edit().remove(key.toString()).apply() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 69fe46aa9009..529d68407ce9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -109,7 +109,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy // Only read and modified in main thread (where click events come through). private int mClickEventId = 0; - private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final ArraySet<Callback> mCallbacks = new ArraySet<>(); private final Object mStaleListener = new Object(); protected TState mState; private TState mTmpState; @@ -444,9 +444,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } private void handleStateChanged() { - if (mCallbacks.size() != 0) { + if (!mCallbacks.isEmpty()) { for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onStateChanged(mState); + mCallbacks.valueAt(i).onStateChanged(mState); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index 94137c88098e..4a34276671c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.di +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.CustomTileStatePersisterImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent @@ -52,4 +54,7 @@ interface QSTilesModule { fun bindQSTileIntentUserInputHandler( impl: QSTileIntentUserInputHandlerImpl ): QSTileIntentUserInputHandler + + @Binds + fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 5bdb592a3558..db3cf0f70f69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -81,10 +81,8 @@ constructor( private lateinit var toggleView: Switch private lateinit var subtitleTextView: TextView private lateinit var doneButton: View - private lateinit var seeAllViewGroup: View - private lateinit var pairNewDeviceViewGroup: View - private lateinit var seeAllRow: View - private lateinit var pairNewDeviceRow: View + private lateinit var seeAllButton: View + private lateinit var pairNewDeviceButton: View private lateinit var deviceListView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { @@ -99,10 +97,8 @@ constructor( toggleView = requireViewById(R.id.bluetooth_toggle) subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView doneButton = requireViewById(R.id.done_button) - seeAllViewGroup = requireViewById(R.id.see_all_layout_group) - pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group) - seeAllRow = requireViewById(R.id.see_all_clickable_row) - pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row) + seeAllButton = requireViewById(R.id.see_all_button) + pairNewDeviceButton = requireViewById(R.id.pair_new_device_button) deviceListView = requireViewById<RecyclerView>(R.id.device_list) setupToggle() @@ -110,8 +106,8 @@ constructor( subtitleTextView.text = context.getString(subtitleResIdInitialValue) doneButton.setOnClickListener { dismiss() } - seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } - pairNewDeviceRow.setOnClickListener { + seeAllButton.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } + pairNewDeviceButton.setOnClickListener { bluetoothTileDialogCallback.onPairNewDeviceClicked(it) } } @@ -134,8 +130,8 @@ constructor( } if (isActive) { deviceItemAdapter.refreshDeviceItemList(deviceItem) { - seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE - pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + seeAllButton.visibility = if (showSeeAll) VISIBLE else GONE + pairNewDeviceButton.visibility = if (showPairNewDevice) VISIBLE else GONE lastUiUpdateMs = systemClock.elapsedRealtime() lastItemRow = itemRow logger.logDeviceUiUpdate(lastUiUpdateMs - start) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index 34c2aba1a71f..5d5e747ba979 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View -import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogCuj @@ -40,6 +39,8 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.produce import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -63,26 +64,25 @@ constructor( private var job: Job? = null - @VisibleForTesting internal var dialog: BluetoothTileDialog? = null - /** * Shows the dialog. * * @param context The context in which the dialog is displayed. * @param view The view from which the dialog is shown. */ + @kotlinx.coroutines.ExperimentalCoroutinesApi fun showDialog(context: Context, view: View?) { - dismissDialog() - - var updateDeviceItemJob: Job? = null - var updateDialogUiJob: Job? = null + cancelJob() job = coroutineScope.launch(mainDispatcher) { - dialog = createBluetoothTileDialog(context) + var updateDeviceItemJob: Job? + var updateDialogUiJob: Job? = null + val dialog = createBluetoothTileDialog(context) + view?.let { dialogLaunchAnimator.showFromView( - dialog!!, + dialog, it, animateBackgroundBoundsChange = true, cuj = @@ -92,9 +92,8 @@ constructor( ) ) } - ?: dialog!!.show() + ?: dialog.show() - updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) } @@ -102,7 +101,7 @@ constructor( bluetoothStateInteractor.bluetoothStateUpdate .filterNotNull() .onEach { - dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it)) + dialog.onBluetoothStateUpdated(it, getSubtitleResId(it)) updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems( @@ -129,7 +128,7 @@ constructor( .onEach { updateDialogUiJob?.cancel() updateDialogUiJob = launch { - dialog?.onDeviceItemUpdated( + dialog.onDeviceItemUpdated( it.take(MAX_DEVICE_ITEM_ENTRY), showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled @@ -138,15 +137,15 @@ constructor( } .launchIn(this) - dialog!! - .bluetoothStateToggle + dialog.bluetoothStateToggle .onEach { bluetoothStateInteractor.isBluetoothEnabled = it } .launchIn(this) - dialog!! - .deviceItemClick + dialog.deviceItemClick .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) } .launchIn(this) + + produce<Unit> { awaitClose { dialog.cancel() } } } } @@ -161,7 +160,7 @@ constructor( logger, context ) - .apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } } + .apply { SystemUIDialog.registerDismissListener(this) { cancelJob() } } } override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) { @@ -188,15 +187,13 @@ constructor( startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view) } - private fun dismissDialog() { + private fun cancelJob() { job?.cancel() job = null - dialog?.dismiss() - dialog = null } private fun startSettingsActivity(intent: Intent, view: View) { - dialog?.run { + if (job?.isActive == true) { intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP activityStarter.postStartActivityDismissingKeyguard( intent, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt index 76fbf8e427e7..fcd45a6431bb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt @@ -168,26 +168,30 @@ constructor( ) } - internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) { - logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type) - - deviceItem.cachedBluetoothDevice.apply { - when (deviceItem.type) { - DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> { - disconnect() - uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT) - } - DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { - setActive() - uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE) - } - DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> { - disconnect() - uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT) - } - DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { - connect() - uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) + internal suspend fun updateDeviceItemOnClick(deviceItem: DeviceItem) { + withContext(backgroundDispatcher) { + logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type) + + deviceItem.cachedBluetoothDevice.apply { + when (deviceItem.type) { + DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> { + disconnect() + uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT) + } + DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { + setActive() + uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE) + } + DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> { + disconnect() + uiEventLogger.log( + BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT + ) + } + DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { + connect() + uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt index c390695b1911..cfb544226c83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -16,8 +16,9 @@ package com.android.systemui.qs.tiles.impl.airplane.domain -import android.content.Context +import android.content.res.Resources import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig @@ -26,29 +27,27 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [AirplaneModeTileModel] to [QSTileState]. */ -class AirplaneModeMapper @Inject constructor(private val context: Context) : +class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) : QSTileDataToStateMapper<AirplaneModeTileModel> { override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = - QSTileState.build(context, config.uiConfig) { + QSTileState.build(resources, config.uiConfig) { val icon = - Icon.Loaded( - context.getDrawable( - if (data.isEnabled) { - R.drawable.qs_airplane_icon_on - } else { - R.drawable.qs_airplane_icon_off - } - )!!, + Icon.Resource( + if (data.isEnabled) { + R.drawable.qs_airplane_icon_on + } else { + R.drawable.qs_airplane_icon_off + }, contentDescription = null ) this.icon = { icon } if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE - secondaryLabel = context.resources.getStringArray(R.array.tile_states_airplane)[2] + secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2] } else { activationState = QSTileState.ActivationState.INACTIVE - secondaryLabel = context.resources.getStringArray(R.array.tile_states_airplane)[1] + secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[1] } contentDescription = label supportedActions = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt new file mode 100644 index 000000000000..869f6f321d21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.impl.custom.commons + +import android.service.quicksettings.Tile + +fun Tile.copy(): Tile = + Tile().also { + it.icon = icon + it.label = label + it.subtitle = subtitle + it.contentDescription = contentDescription + it.stateDescription = stateDescription + it.activityLaunchForClick = activityLaunchForClick + it.state = state + } + +fun Tile.setFrom(otherTile: Tile) { + if (otherTile.icon != null) { + icon = otherTile.icon + } + if (otherTile.customLabel != null) { + label = otherTile.customLabel + } + if (otherTile.subtitle != null) { + subtitle = otherTile.subtitle + } + if (otherTile.contentDescription != null) { + contentDescription = otherTile.contentDescription + } + if (otherTile.stateDescription != null) { + stateDescription = otherTile.stateDescription + } + activityLaunchForClick = otherTile.activityLaunchForClick + state = otherTile.state +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt new file mode 100644 index 000000000000..ca5302e13545 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 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.impl.custom.data.repository + +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.commons.copy +import com.android.systemui.qs.tiles.impl.custom.commons.setFrom +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import com.android.systemui.qs.tiles.impl.di.QSTileScope +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext + +/** + * Repository store the [Tile] associated with the custom tile. It lives on [QSTileScope] which + * allows it to survive service rebinding. Given that, it provides the last received state when + * connected again. + */ +interface CustomTileRepository { + + /** + * Restores the [Tile] if it's [isPersistable]. Restored [Tile] will be available via [getTile] + * (but there is no guarantee that restoration is synchronous) and emitted in [getTiles] for a + * corresponding [user]. + */ + suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) + + /** Returns [Tile] updates for a [user]. */ + fun getTiles(user: UserHandle): Flow<Tile> + + /** + * Return current [Tile] for a [user] or null if the [user] doesn't match currently cached one. + * Suspending until [getTiles] returns something is a way to wait for this to become available. + * + * @throws IllegalStateException when there is no current tile. + */ + fun getTile(user: UserHandle): Tile? + + /** + * Updates tile with the non-null values from [newTile]. Overwrites the current cache when + * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly + * loaded when the [restoreForTheUserIfNeeded]. + */ + suspend fun updateWithTile( + user: UserHandle, + newTile: Tile, + isPersistable: Boolean, + ) + + /** + * Updates tile with the values from [defaults]. Overwrites the current cache when [user] + * differs from the cached one. [isPersistable] tile will be persisted to be possibly loaded + * when the [restoreForTheUserIfNeeded]. + */ + suspend fun updateWithDefaults( + user: UserHandle, + defaults: CustomTileDefaults, + isPersistable: Boolean, + ) +} + +@QSTileScope +class CustomTileRepositoryImpl +@Inject +constructor( + private val tileSpec: TileSpec.CustomTileSpec, + private val customTileStatePersister: CustomTileStatePersister, + @Background private val backgroundContext: CoroutineContext, +) : CustomTileRepository { + + private val tileUpdateMutex = Mutex() + private val tileWithUserState = + MutableSharedFlow<TileWithUser>(onBufferOverflow = BufferOverflow.DROP_OLDEST, replay = 1) + + override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) { + if (isPersistable && getCurrentTileWithUser()?.user != user) { + withContext(backgroundContext) { + customTileStatePersister.readState(user.getKey())?.let { + updateWithTile( + user, + it, + true, + ) + } + } + } + } + + override fun getTiles(user: UserHandle): Flow<Tile> = + tileWithUserState.filter { it.user == user }.map { it.tile } + + override fun getTile(user: UserHandle): Tile? { + val tileWithUser = + getCurrentTileWithUser() ?: throw IllegalStateException("Tile is not set") + return if (tileWithUser.user == user) { + tileWithUser.tile + } else { + null + } + } + + override suspend fun updateWithTile( + user: UserHandle, + newTile: Tile, + isPersistable: Boolean, + ) = updateTile(user, isPersistable) { setFrom(newTile) } + + override suspend fun updateWithDefaults( + user: UserHandle, + defaults: CustomTileDefaults, + isPersistable: Boolean, + ) { + if (defaults is CustomTileDefaults.Result) { + updateTile(user, isPersistable) { + // Update the icon if it's not set or is the default icon. + val updateIcon = (icon == null || icon.isResourceEqual(defaults.icon)) + if (updateIcon) { + icon = defaults.icon + } + setDefaultLabel(defaults.label) + } + } + } + + private suspend fun updateTile( + user: UserHandle, + isPersistable: Boolean, + update: Tile.() -> Unit + ): Unit = + tileUpdateMutex.withLock { + val currentTileWithUser = getCurrentTileWithUser() + val tileToUpdate = + if (currentTileWithUser?.user == user) { + currentTileWithUser.tile.copy() + } else { + Tile() + } + tileToUpdate.update() + if (isPersistable) { + withContext(backgroundContext) { + customTileStatePersister.persistState(user.getKey(), tileToUpdate) + } + } + tileWithUserState.tryEmit(TileWithUser(user, tileToUpdate)) + } + + private fun getCurrentTileWithUser(): TileWithUser? = tileWithUserState.replayCache.lastOrNull() + + /** Compare two icons, only works for resources. */ + private fun Icon.isResourceEqual(icon2: Icon?): Boolean { + if (icon2 == null) { + return false + } + if (this === icon2) { + return true + } + if (type != Icon.TYPE_RESOURCE || icon2.type != Icon.TYPE_RESOURCE) { + return false + } + if (resId != icon2.resId) { + return false + } + return resPackage == icon2.resPackage + } + + private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier) + + private data class TileWithUser(val user: UserHandle, val tile: Tile) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt index 83767aa9d444..d956fdebcd32 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt @@ -24,6 +24,8 @@ import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import dagger.Binds @@ -50,4 +52,6 @@ interface CustomTileModule { fun bindCustomTileDefaultsRepository( impl: CustomTileDefaultsRepositoryImpl ): CustomTileDefaultsRepository + + @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt new file mode 100644 index 000000000000..351bba538463 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 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.impl.custom.domain.interactor + +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.external.TileServiceManager +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository +import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** Manages updates of the [Tile] assigned for the current custom tile. */ +@CustomTileBoundScope +class CustomTileInteractor +@Inject +constructor( + private val user: UserHandle, + private val defaultsRepository: CustomTileDefaultsRepository, + private val customTileRepository: CustomTileRepository, + private val tileServiceManager: TileServiceManager, + @CustomTileBoundScope private val boundScope: CoroutineScope, + @Background private val backgroundContext: CoroutineContext, +) { + + private val tileUpdates = + MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + /** [Tile] updates. [updateTile] to emit a new one. */ + val tiles: Flow<Tile> + get() = customTileRepository.getTiles(user) + + /** + * Current [Tile] + * + * @throws IllegalStateException when the repository stores a tile for another user. This means + * the tile hasn't been updated for the current user. Can happen when this is accessed before + * [init] returns. + */ + val tile: Tile + get() = + customTileRepository.getTile(user) + ?: throw IllegalStateException("Attempt to get a tile for a wrong user") + + /** + * Initializes the repository for the current user. Suspends until it's safe to call [tile] + * which needs at least one of the following: + * - defaults are loaded; + * - receive tile update in [updateTile]; + * - restoration happened for a persisted tile. + */ + suspend fun init() { + launchUpdates() + customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile) + // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or + // tile update. + customTileRepository.getTiles(user).firstOrNull() + } + + private fun launchUpdates() { + tileUpdates + .onEach { + customTileRepository.updateWithTile( + user, + it, + tileServiceManager.isActiveTile, + ) + } + .flowOn(backgroundContext) + .launchIn(boundScope) + defaultsRepository + .defaults(user) + .onEach { + customTileRepository.updateWithDefaults( + user, + it, + tileServiceManager.isActiveTile, + ) + } + .flowOn(backgroundContext) + .launchIn(boundScope) + } + + /** Updates current [Tile]. Emits a new event in [tiles]. */ + fun updateTile(newTile: Tile) { + tileUpdates.tryEmit(newTile) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt index b2b226464ee5..881a6bd156d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -16,8 +16,9 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain -import android.content.Context +import android.content.res.Resources import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig @@ -26,30 +27,28 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [FlashlightTileModel] to [QSTileState]. */ -class FlashlightMapper @Inject constructor(private val context: Context) : +class FlashlightMapper @Inject constructor(@Main private val resources: Resources) : QSTileDataToStateMapper<FlashlightTileModel> { override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = - QSTileState.build(context, config.uiConfig) { + QSTileState.build(resources, config.uiConfig) { val icon = - Icon.Loaded( - context.resources.getDrawable( - if (data.isEnabled) { - R.drawable.qs_flashlight_icon_on - } else { - R.drawable.qs_flashlight_icon_off - } - ), + Icon.Resource( + if (data.isEnabled) { + R.drawable.qs_flashlight_icon_on + } else { + R.drawable.qs_flashlight_icon_off + }, contentDescription = null ) this.icon = { icon } if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE - secondaryLabel = context.resources.getStringArray(R.array.tile_states_flashlight)[2] + secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2] } else { activationState = QSTileState.ActivationState.INACTIVE - secondaryLabel = context.resources.getStringArray(R.array.tile_states_flashlight)[1] + secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1] } contentDescription = label supportedActions = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt index 8e53723a5a6b..7e7034d65efd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -16,8 +16,9 @@ package com.android.systemui.qs.tiles.impl.location.domain -import android.content.Context +import android.content.res.Resources import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig @@ -26,32 +27,30 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [LocationTileModel] to [QSTileState]. */ -class LocationTileMapper @Inject constructor(private val context: Context) : +class LocationTileMapper @Inject constructor(@Main private val resources: Resources) : QSTileDataToStateMapper<LocationTileModel> { override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = - QSTileState.build(context, config.uiConfig) { + QSTileState.build(resources, config.uiConfig) { val icon = - Icon.Loaded( - context.resources.getDrawable( - if (data.isEnabled) { - R.drawable.qs_location_icon_on - } else { - R.drawable.qs_location_icon_off - } - ), + Icon.Resource( + if (data.isEnabled) { + R.drawable.qs_location_icon_on + } else { + R.drawable.qs_location_icon_off + }, contentDescription = null ) this.icon = { icon } - this.label = context.resources.getString(R.string.quick_settings_location_label) + this.label = resources.getString(R.string.quick_settings_location_label) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE - secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[2] + secondaryLabel = resources.getStringArray(R.array.tile_states_location)[2] } else { activationState = QSTileState.ActivationState.INACTIVE - secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[1] + secondaryLabel = resources.getStringArray(R.array.tile_states_location)[1] } contentDescription = label supportedActions = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index f9e0b160acd6..23e0cb66bb6a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.tiles.viewmodel -import android.content.Context +import android.content.res.Resources import android.service.quicksettings.Tile import android.view.View import android.widget.Switch @@ -46,13 +46,13 @@ data class QSTileState( companion object { fun build( - context: Context, + resources: Resources, config: QSTileUIConfig, build: Builder.() -> Unit ): QSTileState = build( { Icon.Resource(config.iconRes, null) }, - context.getString(config.labelRes), + resources.getString(config.labelRes), build, ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index f3f9c916d705..d42fde617394 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -128,7 +128,7 @@ constructor( private fun automaticallySwitchScenes() { applicationScope.launch { // TODO (b/308001302): Move this to a bouncer specific interactor. - bouncerInteractor.onImeHidden.collectLatest { + bouncerInteractor.onImeHiddenByUser.collectLatest { if (sceneInteractor.desiredScene.value.key == SceneKey.Bouncer) { sceneInteractor.changeScene( scene = SceneModel(SceneKey.Lockscreen), @@ -146,19 +146,21 @@ constructor( isAnySimLocked -> { switchToScene( targetSceneKey = SceneKey.Bouncer, - loggingReason = "Need to authenticate locked sim card." + loggingReason = "Need to authenticate locked SIM card." ) } - isUnlocked && !canSwipeToEnter -> { + isUnlocked && canSwipeToEnter == false -> { switchToScene( targetSceneKey = SceneKey.Gone, - loggingReason = "Sim cards are unlocked." + loggingReason = "All SIM cards unlocked and device already" + + " unlocked and lockscreen doesn't require a swipe to dismiss." ) } else -> { switchToScene( targetSceneKey = SceneKey.Lockscreen, - loggingReason = "Sim cards are unlocked." + loggingReason = "All SIM cards unlocked and device still locked" + + " or lockscreen still requires a swipe to dismiss." ) } } @@ -205,11 +207,17 @@ constructor( // when the user is passively authenticated, the false value here // when the unlock state changes indicates this is an active // authentication attempt. - if (isBypassEnabled || !canSwipeToEnter) - SceneKey.Gone to - "device has been unlocked on lockscreen with either " + - "bypass enabled or using an active authentication mechanism" - else null + when { + isBypassEnabled -> + SceneKey.Gone to + "device has been unlocked on lockscreen with bypass" + + " enabled" + canSwipeToEnter == false -> + SceneKey.Gone to + "device has been unlocked on lockscreen using an active" + + " authentication mechanism" + else -> null + } // Not on lockscreen or bouncer, so remain in the current scene. else -> null } @@ -232,7 +240,7 @@ constructor( } else { val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value val isUnlocked = deviceEntryInteractor.isUnlocked.value - if (isUnlocked && !canSwipeToEnter) { + if (isUnlocked && canSwipeToEnter == false) { switchToScene( targetSceneKey = SceneKey.Gone, loggingReason = diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index d14ef35027a3..dbb58a329272 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -16,12 +16,14 @@ package com.android.systemui.scene.shared.flag +import android.content.Context import androidx.annotation.VisibleForTesting import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags.sceneContainer import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flag import com.android.systemui.flags.Flags @@ -29,6 +31,7 @@ import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl +import com.android.systemui.res.R import dagger.Module import dagger.Provides import dagger.assisted.Assisted @@ -51,6 +54,7 @@ interface SceneContainerFlags { class SceneContainerFlagsImpl @AssistedInject constructor( + @Application private val context: Context, private val featureFlagsClassic: FeatureFlagsClassic, @Assisted private val isComposeAvailable: Boolean, ) : SceneContainerFlags { @@ -80,7 +84,11 @@ constructor( ), ) + classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } + - listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled()) + listOf( + ComposeMustBeAvailable(), + CompileTimeFlagMustBeEnabled(), + ResourceConfigMustBeEnabled() + ) override fun isEnabled(): Boolean { // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream @@ -146,6 +154,14 @@ constructor( } } + private inner class ResourceConfigMustBeEnabled : Requirement { + override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true" + + override fun isMet(): Boolean { + return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled) + } + } + @AssistedFactory interface Factory { fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index e7481ccd0efd..b98093e50920 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -32,13 +32,16 @@ import android.os.Handler; import android.os.Looper; import android.os.ResultReceiver; import android.view.Gravity; +import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget; @@ -115,6 +118,17 @@ public class ScreenRecordDialog extends SystemUIDialog { mOptions.setOnItemClickListenerInt((parent, view, position, id) -> { mAudioSwitch.setChecked(true); }); + + // disable redundant Touch & Hold accessibility action for Switch Access + mOptions.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(@NonNull View host, + @NonNull AccessibilityNodeInfo info) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + super.onInitializeAccessibilityNodeInfo(host, info); + } + }); + mOptions.setLongClickable(false); } /** diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 9f4ea27b9ee6..d13edf01cc4a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -39,6 +39,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -58,18 +59,21 @@ public class BrightnessDialog extends Activity { private final DelayableExecutor mMainExecutor; private final AccessibilityManagerWrapper mAccessibilityMgr; private Runnable mCancelTimeoutRunnable; + private final ShadeInteractor mShadeInteractor; @Inject public BrightnessDialog( BrightnessSliderController.Factory brightnessSliderfactory, BrightnessController.Factory brightnessControllerFactory, @Main DelayableExecutor mainExecutor, - AccessibilityManagerWrapper accessibilityMgr + AccessibilityManagerWrapper accessibilityMgr, + ShadeInteractor shadeInteractor ) { mToggleSliderFactory = brightnessSliderfactory; mBrightnessControllerFactory = brightnessControllerFactory; mMainExecutor = mainExecutor; mAccessibilityMgr = accessibilityMgr; + mShadeInteractor = shadeInteractor; } @@ -79,6 +83,10 @@ public class BrightnessDialog extends Activity { setWindowAttributes(); setContentView(R.layout.brightness_mirror_container); setBrightnessDialogViewAttributes(); + + if (mShadeInteractor.isQsExpanded().getValue()) { + finish(); + } } private void setWindowAttributes() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 2a071def083a..0065db370b12 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -66,10 +66,10 @@ constructor( private fun upDestinationSceneKey( isUnlocked: Boolean, - canSwipeToDismiss: Boolean, + canSwipeToDismiss: Boolean?, ): SceneKey { return when { - canSwipeToDismiss -> SceneKey.Lockscreen + canSwipeToDismiss == true -> SceneKey.Lockscreen isUnlocked -> SceneKey.Gone else -> SceneKey.Lockscreen } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 1b096b592a4a..c1a630f48232 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -612,7 +612,7 @@ public final class KeyboardShortcutListSearch { private static KeyboardShortcutMultiMappingGroup getMultiMappingInputShortcuts( Context context) { List<ShortcutMultiMappingInfo> shortcutMultiMappingInfoList = Arrays.asList( - /* Switch input language (next language): Ctrl + Space or Meta + Space */ + /* Switch input language (next language): Ctrl + Space */ new ShortcutMultiMappingInfo( context.getString(R.string.input_switch_input_language_next), null, @@ -621,14 +621,9 @@ public final class KeyboardShortcutListSearch { context.getString( R.string.input_switch_input_language_next), KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON), - null), - new ShortcutKeyGroup(new KeyboardShortcutInfo( - context.getString( - R.string.input_switch_input_language_next), - KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON), null))), /* Switch input language (previous language): */ - /* Ctrl + Shift + Space or Meta + Shift + Space */ + /* Ctrl + Shift + Space */ new ShortcutMultiMappingInfo( context.getString(R.string.input_switch_input_language_previous), null, @@ -638,12 +633,6 @@ public final class KeyboardShortcutListSearch { R.string.input_switch_input_language_previous), KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON), - null), - new ShortcutKeyGroup(new KeyboardShortcutInfo( - context.getString( - R.string.input_switch_input_language_previous), - KeyEvent.KEYCODE_SPACE, - KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON), null))) ); return new KeyboardShortcutMultiMappingGroup( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 0e83c78edb1d..e486457b89bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -530,16 +530,12 @@ public class NotificationLockscreenUserManagerImpl implements userHandle = mCurrentUserId; } if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe - // default value before moving to 'released' - Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable()); - updateUserShowPrivateSettings(userHandle); + Log.i(TAG, "Asking for redact notifs setting too early", new Throwable()); + return false; } if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe - // default value before moving to 'released' - Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable()); - updateDpcSettings(userHandle); + Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable()); + return false; } return mUsersUsersAllowingPrivateNotifications.get(userHandle) && mUsersDpcAllowingPrivateNotifications.get(userHandle); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index e90ddf98db00..31893b402e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -15,48 +15,37 @@ package com.android.systemui.statusbar.notification.domain.interactor -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map class ActiveNotificationsInteractor @Inject constructor( private val repository: ActiveNotificationListRepository, - @Background private val backgroundDispatcher: CoroutineDispatcher, ) { /** Notifications actively presented to the user in the notification stack, in order. */ val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> = - repository.activeNotifications - .map { store -> - store.renderList.map { key -> - val entry = - store[key] - ?: error( - "Could not find notification with key $key in active notif store." - ) - when (entry) { - is ActiveNotificationGroupModel -> entry.summary - is ActiveNotificationModel -> entry - } + repository.activeNotifications.map { store -> + store.renderList.map { key -> + val entry = + store[key] + ?: error("Could not find notification with key $key in active notif store.") + when (entry) { + is ActiveNotificationGroupModel -> entry.summary + is ActiveNotificationModel -> entry } } - .flowOn(backgroundDispatcher) + } /** Are any notifications being actively presented in the notification stack? */ val areAnyNotificationsPresent: Flow<Boolean> = - repository.activeNotifications - .map { it.renderList.isNotEmpty() } - .distinctUntilChanged() - .flowOn(backgroundDispatcher) + repository.activeNotifications.map { it.renderList.isNotEmpty() }.distinctUntilChanged() /** * The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous @@ -70,7 +59,6 @@ constructor( repository.notifStats .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs } .distinctUntilChanged() - .flowOn(backgroundDispatcher) fun setNotifStats(notifStats: NotifStats) { repository.notifStats.value = notifStats diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt index 73341dbc4999..87b8e55dbd1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt @@ -15,24 +15,19 @@ */ package com.android.systemui.statusbar.notification.domain.interactor -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOn /** Domain logic pertaining to notifications on the keyguard. */ class NotificationsKeyguardInteractor @Inject constructor( repository: NotificationsKeyguardViewStateRepository, - @Background backgroundDispatcher: CoroutineDispatcher, ) { /** Is a pulse expansion occurring? */ - val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding.flowOn(backgroundDispatcher) + val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding /** Are notifications fully hidden from view? */ - val areNotificationsFullyHidden: Flow<Boolean> = - repository.areNotificationsFullyHidden.flowOn(backgroundDispatcher) + val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 64f61d9ac2da..8eda96f62257 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -27,7 +27,6 @@ import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -43,11 +42,9 @@ import java.util.Arrays; * A view that can be used for both the dimmed and normal background of an notification. */ public class NotificationBackgroundView extends View implements Dumpable { - private static final String TAG = "NotificationBackgroundView"; private final boolean mDontModifyCorners; private Drawable mBackground; - private Drawable mBackgroundDrawableToTint; private int mClipTopAmount; private int mClipBottomAmount; private int mTintColor; @@ -134,7 +131,6 @@ public class NotificationBackgroundView extends View implements Dumpable { unscheduleDrawable(mBackground); } mBackground = background; - mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground); mRippleColor = null; mBackground.mutate(); if (mBackground != null) { @@ -148,46 +144,25 @@ public class NotificationBackgroundView extends View implements Dumpable { invalidate(); } - // setCustomBackground should be called from ActivatableNotificationView.initBackground - // with R.drawable.notification_material_bg, which is a layer-list with a lower layer - // for the background color (annotated with an ID so we can find it) and an upper layer - // to blend in the stateful @color/notification_overlay_color. - // - // If the notification is tinted, we want to set a tint list on *just that lower layer* that - // will replace the default materialColorSurfaceContainerHigh *without* wiping out the stateful - // tints in the upper layer that make the hovered and pressed states visible. - // - // This function fishes that lower layer out, or makes a fuss in logcat if it can't find it. - private @Nullable Drawable findBackgroundDrawableToTint(@Nullable Drawable background) { - if (background == null) { - return null; - } - - if (!(background instanceof LayerDrawable)) { - Log.wtf(TAG, "background is not a LayerDrawable: " + background); - return background; - } - - final Drawable backgroundColorLayer = ((LayerDrawable) background).findDrawableByLayerId( - R.id.notification_background_color_layer); - - if (backgroundColorLayer == null) { - Log.wtf(TAG, "background is missing background color layer: " + background); - return background; - } - - return backgroundColorLayer; - } - public void setCustomBackground(int drawableResId) { final Drawable d = mContext.getDrawable(drawableResId); setCustomBackground(d); } public void setTint(int tintColor) { - mBackgroundDrawableToTint.setTint(tintColor); - mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP); - + if (tintColor != 0) { + ColorStateList stateList = new ColorStateList(new int[][]{ + new int[]{com.android.internal.R.attr.state_pressed}, + new int[]{com.android.internal.R.attr.state_hovered}, + new int[]{}}, + + new int[]{tintColor, tintColor, tintColor} + ); + mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP); + mBackground.setTintList(stateList); + } else { + mBackground.setTintList(null); + } mTintColor = tintColor; invalidate(); } 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 6d8ec44ad55e..c615887d5c25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -67,6 +67,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; + private boolean mIsSceneContainerVisible = false; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; @@ -128,11 +129,14 @@ public final class StatusBarTouchableRegionManager implements Dumpable { }); mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); if (sceneContainerFlags.isEnabled()) { javaAdapter.alwaysCollectFlow( sceneInteractor.get().isVisible(), + this::onSceneContainerVisibilityChanged); + } else { + javaAdapter.alwaysCollectFlow( + shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); } @@ -164,6 +168,17 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } } + private void onSceneContainerVisibilityChanged(Boolean isVisible) { + if (isVisible != mIsSceneContainerVisible) { + mIsSceneContainerVisible = isVisible; + if (isVisible) { + // make sure our state is sensible + mForceCollapsedUntilLayout = false; + } + updateTouchableRegion(); + } + } + /** * Calculates the touch region needed for heads up notifications, taking into consideration * any existing display cutouts (notch) @@ -267,6 +282,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded + || mIsSceneContainerVisible || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java index bbba19d61b5a..87df180353b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java @@ -73,7 +73,11 @@ public interface DevicePostureController extends CallbackController<Callback> { /** Callback to be notified about device posture changes. */ interface Callback { - /** Called when the posture changes. */ + /** + * Called when the posture changes. If there are multiple active displays ("concurrent"), + * this will report the physical posture of the device (also known as the base device + * state). + */ void onPostureChanged(@DevicePostureInt int posture); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java index 8f1ac812da71..422aa4d4aa60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java @@ -39,8 +39,11 @@ import javax.inject.Inject; /** Implementation of {@link DevicePostureController} using the DeviceStateManager. */ @SysUISingleton public class DevicePostureControllerImpl implements DevicePostureController { + /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */ + private static final int COMMON_STATE_USE_BASE_STATE = 1000; private final List<Callback> mListeners = new ArrayList<>(); private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN; + private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN; private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); @@ -73,16 +76,32 @@ public class DevicePostureControllerImpl implements DevicePostureController { mDeviceStateToPostureMap.put(deviceState, posture); } - deviceStateManager.registerCallback(executor, state -> { - Assert.isMainThread(); - mCurrentDevicePosture = - mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); + deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() { + @Override + public void onStateChanged(int state) { + Assert.isMainThread(); + mCurrentDevicePosture = + mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); + sendUpdatePosture(); + } + + @Override + public void onBaseStateChanged(int state) { + Assert.isMainThread(); + mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); + + if (useBaseState()) { + sendUpdatePosture(); + } + } - ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl", + private void sendUpdatePosture() { + ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl", l -> { - l.onPostureChanged(mCurrentDevicePosture); + l.onPostureChanged(getDevicePosture()); return Unit.INSTANCE; }); + } }); } @@ -100,6 +119,14 @@ public class DevicePostureControllerImpl implements DevicePostureController { @Override public int getDevicePosture() { - return mCurrentDevicePosture; + if (useBaseState()) { + return mCurrentBasePosture; + } else { + return mCurrentDevicePosture; + } + } + + private boolean useBaseState() { + return mCurrentDevicePosture == COMMON_STATE_USE_BASE_STATE; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java index e576f36d573a..279e5ef1f38c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; public interface FlashlightController extends CallbackController<FlashlightListener>, Dumpable { @@ -24,6 +25,7 @@ public interface FlashlightController extends CallbackController<FlashlightListe boolean isAvailable(); boolean isEnabled(); + @WeaklyReferencedCallback public interface FlashlightListener { /** diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 053148709e69..968981197b83 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -20,10 +20,12 @@ import android.content.Context import android.hardware.devicestate.DeviceStateManager import android.os.SystemProperties import com.android.systemui.CoreStartable +import com.android.systemui.Flags import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldBgProgressFlag import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl @@ -64,6 +66,10 @@ class UnfoldTransitionModule { @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui" + @Provides + @UnfoldBgProgressFlag + fun unfoldBgProgressFlag() = Flags.unfoldAnimationBackgroundProgress() + /** A globally available FoldStateListener that allows one to query the fold state. */ @Provides @Singleton diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java index d8799e16ebdb..43952824f9a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -133,8 +133,8 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { } @Test - public void setScale() throws RemoteException { - mIWindowMagnificationConnection.setScale(TEST_DISPLAY, 3.0f); + public void setScaleForWindowMagnification() throws RemoteException { + mIWindowMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); waitForIdleSync(); verify(mWindowMagnificationController).setScale(3.0f); 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 f4122d59cea1..ea20d29556dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -338,6 +338,13 @@ open class AuthContainerViewTest : SysuiTestCase() { waitForIdleSync() assertThat(container.hasCredentialView()).isTrue() + assertThat(container.hasBiometricPrompt()).isFalse() + + // Check credential view persists after new attachment + container.onAttachedToWindow() + + assertThat(container.hasCredentialView()).isTrue() + assertThat(container.hasBiometricPrompt()).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 67c4e2688cd0..0c30d10ea563 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences @@ -58,8 +59,10 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth -import kotlin.math.max import kotlin.math.min +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -73,6 +76,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { @@ -85,6 +89,47 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger + @Mock private lateinit var shadeInteractor: ShadeInteractor + @Mock + private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel + @Mock + private lateinit var dozingToLockscreenTransitionViewModel: + DozingToLockscreenTransitionViewModel + @Mock + private lateinit var dreamingHostedToLockscreenTransitionViewModel: + DreamingHostedToLockscreenTransitionViewModel + @Mock + private lateinit var dreamingToLockscreenTransitionViewModel: + DreamingToLockscreenTransitionViewModel + @Mock + private lateinit var goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel + @Mock + private lateinit var occludedToLockscreenTransitionViewModel: + OccludedToLockscreenTransitionViewModel + @Mock + private lateinit var offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel + @Mock + private lateinit var primaryBouncerToLockscreenTransitionViewModel: + PrimaryBouncerToLockscreenTransitionViewModel + @Mock + private lateinit var lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel + @Mock + private lateinit var lockscreenToDozingTransitionViewModel: + LockscreenToDozingTransitionViewModel + @Mock + private lateinit var lockscreenToDreamingHostedTransitionViewModel: + LockscreenToDreamingHostedTransitionViewModel + @Mock + private lateinit var lockscreenToDreamingTransitionViewModel: + LockscreenToDreamingTransitionViewModel + @Mock + private lateinit var lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel + @Mock + private lateinit var lockscreenToOccludedTransitionViewModel: + LockscreenToOccludedTransitionViewModel + @Mock + private lateinit var lockscreenToPrimaryBouncerTransitionViewModel: + LockscreenToPrimaryBouncerTransitionViewModel private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel @@ -97,6 +142,10 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var keyguardInteractor: KeyguardInteractor + private val intendedAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(1f) + // the viewModel does a `map { 1 - it }` on this value, which is why it's different + private val intendedShadeAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(0f) + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -191,6 +240,31 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { userHandle = UserHandle.SYSTEM, ) + intendedAlphaMutableStateFlow.value = 1f + intendedShadeAlphaMutableStateFlow.value = 0f + whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha) + .thenReturn(intendedAlphaMutableStateFlow) + whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(dreamingHostedToLockscreenTransitionViewModel.shortcutsAlpha) + .thenReturn(emptyFlow()) + whenever(dreamingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(goneToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(occludedToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(offToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha) + .thenReturn(emptyFlow()) + whenever(lockscreenToAodTransitionViewModel.shortcutsAlpha) + .thenReturn(intendedAlphaMutableStateFlow) + whenever(lockscreenToDozingTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(lockscreenToDreamingHostedTransitionViewModel.shortcutsAlpha) + .thenReturn(emptyFlow()) + whenever(lockscreenToDreamingTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(lockscreenToGoneTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) + whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha) + .thenReturn(emptyFlow()) + whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow) + underTest = KeyguardQuickAffordancesCombinedViewModel( quickAffordanceInteractor = @@ -210,7 +284,27 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { backgroundDispatcher = testDispatcher, appContext = mContext, ), - keyguardInteractor = keyguardInteractor + keyguardInteractor = keyguardInteractor, + shadeInteractor = shadeInteractor, + aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, + dreamingHostedToLockscreenTransitionViewModel = + dreamingHostedToLockscreenTransitionViewModel, + dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, + goneToLockscreenTransitionViewModel = goneToLockscreenTransitionViewModel, + occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel, + primaryBouncerToLockscreenTransitionViewModel = + primaryBouncerToLockscreenTransitionViewModel, + lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel, + lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel, + lockscreenToDreamingHostedTransitionViewModel = + lockscreenToDreamingHostedTransitionViewModel, + lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel, + lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel, + lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel, + lockscreenToPrimaryBouncerTransitionViewModel = + lockscreenToPrimaryBouncerTransitionViewModel ) } @@ -526,15 +620,15 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { @Test fun isClickable_falseWhenAlphaBelowThreshold() = testScope.runTest { + intendedAlphaMutableStateFlow.value = + KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - + .1f + // the viewModel does a `map { 1 - it }` on this value, which is why it's different + intendedShadeAlphaMutableStateFlow.value = + KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + + .1f repository.setKeyguardShowing(true) val latest = collectLastValue(underTest.startButton) - repository.setKeyguardAlpha( - max( - 0f, - KeyguardQuickAffordancesCombinedViewModel - .AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f - ), - ) val testConfig = TestConfig( @@ -561,9 +655,10 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { @Test fun isClickable_falseWhenAlphaAtZero() = testScope.runTest { + intendedAlphaMutableStateFlow.value = 0f + intendedShadeAlphaMutableStateFlow.value = 1f repository.setKeyguardShowing(true) val latest = collectLastValue(underTest.startButton) - repository.setKeyguardAlpha(0f) val testConfig = TestConfig( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index e6d6cf263d69..a57feda64723 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -163,23 +163,6 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test - fun alpha_inPreviewMode_doesNotChange() = - testScope.runTest { - val value = collectLastValue(underTest.alpha) - underTest.enablePreviewMode() - - assertThat(value()).isEqualTo(1f) - repository.setKeyguardAlpha(0.1f) - assertThat(value()).isEqualTo(1f) - repository.setKeyguardAlpha(0.5f) - assertThat(value()).isEqualTo(1f) - repository.setKeyguardAlpha(0.2f) - assertThat(value()).isEqualTo(1f) - repository.setKeyguardAlpha(0f) - assertThat(value()).isEqualTo(1f) - } - - @Test fun translationAndScaleFromBurnInNotDozing() = testScope.runTest { val translationX by collectLastValue(underTest.translationX) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt index a9f8ea0194c1..81d02b8043b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt @@ -85,7 +85,7 @@ class CustomTileStatePersisterTest : SysuiTestCase() { `when`(sharedPreferences.edit()).thenReturn(editor) tile = Tile() - customTileStatePersister = CustomTileStatePersister(mockContext) + customTileStatePersister = CustomTileStatePersisterImpl(mockContext) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt index 3808c7ee926b..313ccb8a8717 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt @@ -228,16 +228,16 @@ class BluetoothTileDialogTest : SysuiTestCase() { showPairNewDevice = true ) - val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) - val pairNewLayout = - bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) + val seeAllButton = bluetoothTileDialog.requireViewById<View>(R.id.see_all_button) + val pairNewButton = + bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_button) val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(seeAllLayout).isNotNull() - assertThat(seeAllLayout.visibility).isEqualTo(GONE) - assertThat(pairNewLayout).isNotNull() - assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) + assertThat(seeAllButton).isNotNull() + assertThat(seeAllButton.visibility).isEqualTo(GONE) + assertThat(pairNewButton).isNotNull() + assertThat(pairNewButton.visibility).isEqualTo(VISIBLE) assertThat(adapter.itemCount).isEqualTo(1) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index fb5dd212ff22..99993f2b3eff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -113,9 +112,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.runTest { bluetoothTileDialogViewModel.showDialog(context, null) - assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), any()) - assertThat(bluetoothTileDialogViewModel.dialog?.isShowing).isTrue() verify(uiEventLogger).log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN) } } @@ -125,7 +122,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.runTest { bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext)) - assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean()) } } @@ -136,7 +132,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { backgroundExecutor.execute { bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext)) - assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean()) } } @@ -147,7 +142,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.runTest { bluetoothTileDialogViewModel.showDialog(context, null) - assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() verify(deviceItemInteractor).deviceItemUpdate } } @@ -157,7 +151,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.runTest { bluetoothTileDialogViewModel.showDialog(context, null) - assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() verify(bluetoothStateInteractor).bluetoothStateUpdate } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt index 4c173cc2956d..e236f4a7730f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt @@ -220,45 +220,57 @@ class DeviceItemInteractorTest : SysuiTestCase() { @Test fun testUpdateDeviceItemOnClick_connectedMedia_setActive() { - `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) + testScope.runTest { + `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) - interactor.updateDeviceItemOnClick(deviceItem1) + interactor.updateDeviceItemOnClick(deviceItem1) - verify(cachedDevice1).setActive() - verify(logger) - .logDeviceClick(cachedDevice1.address, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) + verify(cachedDevice1).setActive() + verify(logger) + .logDeviceClick( + cachedDevice1.address, + DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE + ) + } } @Test fun testUpdateDeviceItemOnClick_activeMedia_disconnect() { - `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) + testScope.runTest { + `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) - interactor.updateDeviceItemOnClick(deviceItem1) + interactor.updateDeviceItemOnClick(deviceItem1) - verify(cachedDevice1).disconnect() - verify(logger) - .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) + verify(cachedDevice1).disconnect() + verify(logger) + .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) + } } @Test fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() { - `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + testScope.runTest { + `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) - interactor.updateDeviceItemOnClick(deviceItem1) + interactor.updateDeviceItemOnClick(deviceItem1) - verify(cachedDevice1).disconnect() - verify(logger) - .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + verify(cachedDevice1).disconnect() + verify(logger) + .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + } } @Test fun testUpdateDeviceItemOnClick_saved_connect() { - `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) + testScope.runTest { + `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) - interactor.updateDeviceItemOnClick(deviceItem1) + interactor.updateDeviceItemOnClick(deviceItem1) - verify(cachedDevice1).connect() - verify(logger).logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE) + verify(cachedDevice1).connect() + verify(logger) + .logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE) + } } private fun createFactory( diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt index 5969bd82c361..0173c32bbb94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -74,10 +75,15 @@ internal class SceneContainerFlagsTest( .forEach { flagToken -> setFlagsRule.enableFlags(flagToken) aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet) + overrideResource( + R.bool.config_sceneContainerFrameworkEnabled, + testCase.isResourceConfigEnabled + ) } underTest = SceneContainerFlagsImpl( + context = context, featureFlagsClassic = featureFlags, isComposeAvailable = testCase.isComposeAvailable, ) @@ -91,13 +97,12 @@ internal class SceneContainerFlagsTest( internal data class TestCase( val isComposeAvailable: Boolean, val areAllFlagsSet: Boolean, + val isResourceConfigEnabled: Boolean, val expectedEnabled: Boolean, ) { override fun toString(): String { - return """ - (compose=$isComposeAvailable + flags=$areAllFlagsSet) -> expected=$expectedEnabled - """ - .trimIndent() + return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" + + " config=$isResourceConfigEnabled -> expected=$expectedEnabled" } } @@ -105,17 +110,20 @@ internal class SceneContainerFlagsTest( @Parameterized.Parameters(name = "{0}") @JvmStatic fun testCases() = buildList { - repeat(4) { combination -> - val isComposeAvailable = combination and 0b10 != 0 - val areAllFlagsSet = combination and 0b01 != 0 + repeat(8) { combination -> + val isComposeAvailable = combination and 0b100 != 0 + val areAllFlagsSet = combination and 0b010 != 0 + val isResourceConfigEnabled = combination and 0b001 != 0 - val expectedEnabled = isComposeAvailable && areAllFlagsSet + val expectedEnabled = + isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled add( TestCase( isComposeAvailable = isComposeAvailable, areAllFlagsSet = areAllFlagsSet, expectedEnabled = expectedEnabled, + isResourceConfigEnabled = isResourceConfigEnabled, ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 6e487cdd65b5..88c728fd1b66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -28,12 +28,16 @@ import androidx.test.rule.ActivityTestRule import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import dagger.Lazy +import kotlinx.coroutines.flow.MutableStateFlow import org.junit.After import org.junit.Before import org.junit.Rule @@ -55,6 +59,8 @@ class BrightnessDialogTest : SysuiTestCase() { @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory @Mock private lateinit var brightnessController: BrightnessController @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper + @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor> + @Mock private lateinit var shadeInteractor: ShadeInteractor private val clock = FakeSystemClock() private val mainExecutor = FakeExecutor(clock) @@ -68,7 +74,8 @@ class BrightnessDialogTest : SysuiTestCase() { brightnessSliderControllerFactory, brightnessControllerFactory, mainExecutor, - accessibilityMgr + accessibilityMgr, + shadeInteractor ) }, /* initialTouchMode= */ false, @@ -82,6 +89,8 @@ class BrightnessDialogTest : SysuiTestCase() { .thenReturn(brightnessSliderController) `when`(brightnessSliderController.rootView).thenReturn(View(context)) `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController) + whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor) + whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false)) } @After @@ -175,13 +184,15 @@ class BrightnessDialogTest : SysuiTestCase() { brightnessSliderControllerFactory: BrightnessSliderController.Factory, brightnessControllerFactory: BrightnessController.Factory, mainExecutor: DelayableExecutor, - accessibilityMgr: AccessibilityManagerWrapper + accessibilityMgr: AccessibilityManagerWrapper, + shadeInteractor: ShadeInteractor ) : BrightnessDialog( brightnessSliderControllerFactory, brightnessControllerFactory, mainExecutor, - accessibilityMgr + accessibilityMgr, + shadeInteractor ) { private var finishing = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 62c0ebeb8c07..1dbb2972c6f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -22,8 +22,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; - import android.content.res.Resources; import android.os.Handler; import android.os.Looper; @@ -295,10 +293,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ) ); - mActiveNotificationsInteractor = new ActiveNotificationsInteractor( - new ActiveNotificationListRepository(), - StandardTestDispatcher(/* scheduler = */ null, /* name = */ null) - ); + mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(new ActiveNotificationListRepository()); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt index 6374d5e259fc..b86f8410fb7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt @@ -25,19 +25,14 @@ import com.android.systemui.statusbar.notification.shared.byKey import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @SmallTest class RenderNotificationsListInteractorTest : SysuiTestCase() { - private val backgroundDispatcher = StandardTestDispatcher() - private val testScope = TestScope(backgroundDispatcher) private val notifsRepository = ActiveNotificationListRepository() - private val notifsInteractor = - ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher) + private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository) private val underTest = RenderNotificationListInteractor( notifsRepository, @@ -45,26 +40,21 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() { ) @Test - fun setRenderedList_preservesOrdering() = - testScope.runTest { - val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications) - val keys = (1..50).shuffled().map { "$it" } - val entries = - keys.map { - mock<ListEntry> { - val mockRep = - mock<NotificationEntry> { - whenever(key).thenReturn(it) - whenever(sbn).thenReturn(mock()) - whenever(icons).thenReturn(mock()) - } - whenever(representativeEntry).thenReturn(mockRep) + fun setRenderedList_preservesOrdering() = runTest { + val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications) + val keys = (1..50).shuffled().map { "$it" } + val entries = + keys.map { + mock<ListEntry> { + val mockRep = mock<NotificationEntry> { + whenever(key).thenReturn(it) + whenever(sbn).thenReturn(mock()) + whenever(icons).thenReturn(mock()) } + whenever(representativeEntry).thenReturn(mockRep) } - underTest.setRenderedList(entries) - assertThat(notifs) - .comparingElementsUsing(byKey) - .containsExactlyElementsIn(keys) - .inOrder() - } + } + underTest.setRenderedList(entries) + assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 755897442eb6..ff5c02622e4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -35,7 +35,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static kotlinx.coroutines.flow.FlowKt.emptyFlow; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; import android.metrics.LogMaker; import android.testing.AndroidTestingRunner; @@ -170,8 +169,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { new ActiveNotificationListRepository(); private final ActiveNotificationsInteractor mActiveNotificationsInteractor = - new ActiveNotificationsInteractor(mActiveNotificationsRepository, - StandardTestDispatcher(/* scheduler = */ null, /* name = */ null)); + new ActiveNotificationsInteractor(mActiveNotificationsRepository); private final SeenNotificationsInteractor mSeenNotificationsInteractor = new SeenNotificationsInteractor(mActiveNotificationsRepository); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt new file mode 100644 index 000000000000..ce471705ed85 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.hardware.devicestate.DeviceStateManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableResources +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.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.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class DevicePostureControllerImplTest : SysuiTestCase() { + private val useBaseStateDeviceState = SUPPORTED_POSTURES_SIZE + private val deviceStateToPostureMapping = + arrayOf( + "$DEVICE_POSTURE_UNKNOWN:$DEVICE_POSTURE_UNKNOWN", + "$DEVICE_POSTURE_CLOSED:$DEVICE_POSTURE_CLOSED", + "$DEVICE_POSTURE_HALF_OPENED:$DEVICE_POSTURE_HALF_OPENED", + "$DEVICE_POSTURE_OPENED:$DEVICE_POSTURE_OPENED", + "$DEVICE_POSTURE_FLIPPED:$DEVICE_POSTURE_FLIPPED", + "$useBaseStateDeviceState:1000" + ) + @Mock private lateinit var deviceStateManager: DeviceStateManager + @Captor + private lateinit var deviceStateCallback: ArgumentCaptor<DeviceStateManager.DeviceStateCallback> + + private lateinit var mainExecutor: FakeExecutor + private lateinit var testableResources: TestableResources + private lateinit var underTest: DevicePostureControllerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mainExecutor = FakeExecutor(FakeSystemClock()) + testableResources = context.getOrCreateTestableResources() + testableResources.addOverride( + com.android.internal.R.array.config_device_state_postures, + deviceStateToPostureMapping + ) + underTest = + DevicePostureControllerImpl( + context, + deviceStateManager, + mainExecutor, + ) + verifyRegistersForDeviceStateCallback() + } + + @Test + fun testPostureChanged_updates() { + var posture = -1 + underTest.addCallback { posture = it } + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN) + assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN) + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED) + assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED) + assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED) + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED) + assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED) + } + + @Test + fun testPostureChanged_useBaseUpdate() { + var posture = -1 + underTest.addCallback { posture = it } + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) + + // base state change doesn't change the posture + deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) + assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) + + // WHEN the display state maps to using the base state, then posture updates + deviceStateCallback.value.onStateChanged(useBaseStateDeviceState) + assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) + } + + @Test + fun baseStateChanges_doesNotUpdatePosture() { + var numPostureChanges = 0 + underTest.addCallback { numPostureChanges++ } + + deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + assertThat(numPostureChanges).isEqualTo(1) + + // base state changes doesn't send another posture update since the device state isn't + // useBaseStateDeviceState + deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) + deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED) + deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED) + deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN) + assertThat(numPostureChanges).isEqualTo(1) + } + + private fun verifyRegistersForDeviceStateCallback() { + verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt new file mode 100644 index 000000000000..4e61b89b9c3e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 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.unfold.progress + +import android.os.Looper +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.utils.os.FakeHandler +import kotlin.test.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper(setAsMainLooper = true) +class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() { + + private val wrappedProgressProvider = TestUnfoldTransitionProvider() + private val fakeHandler = FakeHandler(Looper.getMainLooper()) + private val listener = TestUnfoldProgressListener() + + private val progressProvider = + MainThreadUnfoldTransitionProgressProvider(fakeHandler, wrappedProgressProvider) + + @Test + fun onTransitionStarted_propagated() { + progressProvider.addCallback(listener) + + wrappedProgressProvider.onTransitionStarted() + fakeHandler.dispatchQueuedMessages() + + listener.assertStarted() + } + + @Test + fun onTransitionProgress_propagated() { + progressProvider.addCallback(listener) + + wrappedProgressProvider.onTransitionStarted() + wrappedProgressProvider.onTransitionProgress(0.5f) + fakeHandler.dispatchQueuedMessages() + + listener.assertLastProgress(0.5f) + } + + @Test + fun onTransitionFinished_propagated() { + progressProvider.addCallback(listener) + + wrappedProgressProvider.onTransitionStarted() + wrappedProgressProvider.onTransitionProgress(0.5f) + wrappedProgressProvider.onTransitionFinished() + fakeHandler.dispatchQueuedMessages() + + listener.ensureTransitionFinished() + } + + @Test + fun onTransitionFinishing_propagated() { + progressProvider.addCallback(listener) + + wrappedProgressProvider.onTransitionStarted() + wrappedProgressProvider.onTransitionProgress(0.5f) + wrappedProgressProvider.onTransitionFinished() + fakeHandler.dispatchQueuedMessages() + + listener.ensureTransitionFinished() + } + + @Test + fun onTransitionStarted_afterCallbackRemoved_notPropagated() { + progressProvider.addCallback(listener) + progressProvider.removeCallback(listener) + + wrappedProgressProvider.onTransitionStarted() + fakeHandler.dispatchQueuedMessages() + + listener.assertNotStarted() + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt new file mode 100644 index 000000000000..29702eb2cdc9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.external + +import android.service.quicksettings.Tile + +class FakeCustomTileStatePersister : CustomTileStatePersister { + + private val tiles: MutableMap<TileServiceKey, Tile> = mutableMapOf() + + override fun readState(key: TileServiceKey): Tile? = tiles[key] + + override fun persistState(key: TileServiceKey, tile: Tile) { + tiles[key] = tile + } + + override fun removeState(key: TileServiceKey) { + tiles.remove(key) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt new file mode 100644 index 000000000000..d2351dc8ae18 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 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.impl.custom + +import android.service.quicksettings.Tile +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.tiles +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Subject.Factory +import com.google.common.truth.Truth + +/** + * [Tile]-specific extension for [Truth]. Use [assertThat] or [tiles] to get an instance of this + * subject. + */ +class TileSubject private constructor(failureMetadata: FailureMetadata, subject: Tile?) : + Subject(failureMetadata, subject) { + + private val actual: Tile? = subject + + /** Asserts if the [Tile] fields are the same. */ + fun isEqualTo(other: Tile?) { + if (actual == null) { + check("other").that(other).isNull() + return + } else { + check("other").that(other).isNotNull() + other ?: return + } + + check("icon").that(actual.icon).isEqualTo(other.icon) + check("label").that(actual.label).isEqualTo(other.label) + check("subtitle").that(actual.subtitle).isEqualTo(other.subtitle) + check("contentDescription") + .that(actual.contentDescription) + .isEqualTo(other.contentDescription) + check("stateDescription").that(actual.stateDescription).isEqualTo(other.stateDescription) + check("activityLaunchForClick") + .that(actual.activityLaunchForClick) + .isEqualTo(other.activityLaunchForClick) + check("state").that(actual.state).isEqualTo(other.state) + } + + companion object { + + /** Returns a factory to be used with [Truth.assertAbout]. */ + fun tiles(): Factory<TileSubject, Tile?> { + return Factory { failureMetadata: FailureMetadata, subject: Tile? -> + TileSubject(failureMetadata, subject) + } + } + + /** Shortcut for `Truth.assertAbout(tiles()).that(tile)`. */ + fun assertThat(tile: Tile?): TileSubject = Truth.assertAbout(tiles()).that(tile) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt index 13910fd5c564..ccba07273f1e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt @@ -19,15 +19,20 @@ package com.android.systemui.qs.tiles.impl.custom.data.repository import android.content.ComponentName import android.os.UserHandle import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull class FakeCustomTileDefaultsRepository : CustomTileDefaultsRepository { private val defaults: MutableMap<DefaultsKey, CustomTileDefaults> = mutableMapOf() - private val defaultsFlow = MutableSharedFlow<DefaultsRequest>() + private val defaultsFlow = + MutableSharedFlow<DefaultsRequest>( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) private val mutableDefaultsRequests: MutableList<DefaultsRequest> = mutableListOf() val defaultsRequests: List<DefaultsRequest> = mutableDefaultsRequests @@ -41,7 +46,7 @@ class FakeCustomTileDefaultsRepository : CustomTileDefaultsRepository { old == new } } - .map { defaults[DefaultsKey(it.user, it.componentName)]!! } + .mapNotNull { defaults[DefaultsKey(it.user, it.componentName)] } override fun requestNewDefaults( user: UserHandle, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt new file mode 100644 index 000000000000..ccf03911495f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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.impl.custom.data.repository + +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.qs.external.FakeCustomTileStatePersister +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.Flow + +class FakeCustomTileRepository( + tileSpec: TileSpec.CustomTileSpec, + customTileStatePersister: FakeCustomTileStatePersister, + testBackgroundContext: CoroutineContext, +) : CustomTileRepository { + + private val realDelegate: CustomTileRepository = + CustomTileRepositoryImpl( + tileSpec, + customTileStatePersister, + testBackgroundContext, + ) + + override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) = + realDelegate.restoreForTheUserIfNeeded(user, isPersistable) + + override fun getTiles(user: UserHandle): Flow<Tile> = realDelegate.getTiles(user) + + override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user) + + override suspend fun updateWithTile( + user: UserHandle, + newTile: Tile, + isPersistable: Boolean, + ) = realDelegate.updateWithTile(user, newTile, isPersistable) + + override suspend fun updateWithDefaults( + user: UserHandle, + defaults: CustomTileDefaults, + isPersistable: Boolean, + ) = realDelegate.updateWithDefaults(user, defaults, isPersistable) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt index 01f453570e63..3d7fb6d91393 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt @@ -17,10 +17,7 @@ package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository val Kosmos.activeNotificationsInteractor by - Kosmos.Fixture { - ActiveNotificationsInteractor(activeNotificationListRepository, testDispatcher) - } + Kosmos.Fixture { ActiveNotificationsInteractor(activeNotificationListRepository) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt index 42d31b38ff76..f7fb01465a40 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt @@ -19,8 +19,10 @@ package com.android.systemui.unfold import android.os.Handler import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.dagger.UnfoldBg +import com.android.systemui.unfold.dagger.UnfoldBgProgressFlag import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider +import com.android.systemui.unfold.progress.MainThreadUnfoldTransitionProgressProvider import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder import com.android.systemui.unfold.updates.DeviceFoldStateProvider @@ -36,15 +38,18 @@ import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManagerImpl import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider +import dagger.BindsOptionalOf import dagger.Module import dagger.Provides import java.util.Optional import javax.inject.Provider import javax.inject.Singleton +import kotlin.jvm.optionals.getOrDefault @Module( includes = [ + UnfoldFlagsModule::class, UnfoldSharedInternalModule::class, UnfoldRotationProviderInternalModule::class, HingeAngleProviderInternalModule::class, @@ -69,6 +74,16 @@ class UnfoldSharedModule { fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl } +@Module +abstract class UnfoldFlagsModule { + /** + * Users of the library can bind this boolean to notify whether the progress should be + * calculated only in the background (and the main thread provider is generated by posting the + * background events in the main handler). + */ + @BindsOptionalOf @UnfoldBgProgressFlag abstract fun unfoldBgProgressFlag(): Boolean +} + /** * Needed as methods inside must be public, but their parameters can be internal (and, a public * method can't have internal parameters). Making the module internal and included in a public one @@ -87,17 +102,34 @@ internal class UnfoldSharedInternalModule { fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, foldStateProvider: FoldStateProvider, @UnfoldMain mainHandler: Handler, + mainThreadUnfoldTransitionProgressProviderFactory: + MainThreadUnfoldTransitionProgressProvider.Factory, + @UnfoldBg bgProvider: Provider<Optional<UnfoldTransitionProgressProvider>>, + @UnfoldBgProgressFlag unfoldBgProgressFlag: Optional<Boolean>, ): Optional<UnfoldTransitionProgressProvider> { - return createOptionalUnfoldTransitionProgressProvider( - config = config, - scaleAwareProviderFactory = scaleAwareProviderFactory, - tracingListener = tracingListener.create("MainThread"), - physicsBasedUnfoldTransitionProgressProvider = - physicsBasedUnfoldTransitionProgressProvider, - fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider, - foldStateProvider = foldStateProvider, - progressHandler = mainHandler, - ) + if (unfoldBgProgressFlag.getOrDefault(false)) { + // In this case, we wrap the background progress provider + val mainThreadProvider: Optional<UnfoldTransitionProgressProvider> = + bgProvider.get().map { + mainThreadUnfoldTransitionProgressProviderFactory.create(it) + } + mainThreadProvider.ifPresent { + it.addCallback(tracingListener.create("MainThreadFromBgProgress")) + } + return mainThreadProvider + } else { + // TODO(b/277879146): Remove this once unfold_animation_background_progress is launched. + return createOptionalUnfoldTransitionProgressProvider( + config = config, + scaleAwareProviderFactory = scaleAwareProviderFactory, + tracingListener = tracingListener.create("MainThread"), + physicsBasedUnfoldTransitionProgressProvider = + physicsBasedUnfoldTransitionProgressProvider, + fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider, + foldStateProvider = foldStateProvider, + progressHandler = mainHandler, + ) + } } @Provides diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBgProgressFlag.kt index 2fe4fd5a12e4..0e371fa6bfb7 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBgProgressFlag.kt @@ -5,7 +5,7 @@ * 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.0N + * 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, @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.credentialmanager.model +package com.android.systemui.unfold.dagger -import android.credentials.ui.Entry -import androidx.credentials.provider.PasswordCredentialEntry +import javax.inject.Qualifier -data class Password( - val providerId: String, - val entry: Entry, - val passwordCredentialEntry: PasswordCredentialEntry, -) +/** + * Annotates the boolean representing whether we are calculating progresses in the background. + * + * Used to allow clients to provide this value, without depending on the flags directly. + */ +@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldBgProgressFlag diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt new file mode 100644 index 000000000000..9bdf3d5d5307 --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 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.unfold.progress + +import android.os.Handler +import androidx.annotation.FloatRange +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.dagger.UnfoldMain +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** + * [UnfoldTransitionProgressProvider] that forwards all progress to the main thread handler. + * + * This is needed when progress are calculated in the background, but some listeners need the + * callbacks in the main thread. + */ +class MainThreadUnfoldTransitionProgressProvider +@AssistedInject +constructor( + @UnfoldMain private val mainHandler: Handler, + @Assisted private val rootProvider: UnfoldTransitionProgressProvider +) : UnfoldTransitionProgressProvider { + + private val listenerMap = mutableMapOf<TransitionProgressListener, TransitionProgressListener>() + + override fun addCallback(listener: TransitionProgressListener) { + assertMainThread() + val proxy = TransitionProgressListerProxy(listener) + rootProvider.addCallback(proxy) + listenerMap[listener] = proxy + } + + override fun removeCallback(listener: TransitionProgressListener) { + assertMainThread() + val proxy = listenerMap.remove(listener) ?: return + rootProvider.removeCallback(proxy) + } + + private fun assertMainThread() { + check(mainHandler.looper.isCurrentThread) { + "Should be called from the main thread, but this is ${Thread.currentThread()}" + } + } + + override fun destroy() { + rootProvider.destroy() + } + + inner class TransitionProgressListerProxy(private val listener: TransitionProgressListener) : + TransitionProgressListener { + override fun onTransitionStarted() { + mainHandler.post { listener.onTransitionStarted() } + } + + override fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) { + mainHandler.post { listener.onTransitionProgress(progress) } + } + + override fun onTransitionFinishing() { + mainHandler.post { listener.onTransitionFinishing() } + } + + override fun onTransitionFinished() { + mainHandler.post { listener.onTransitionFinished() } + } + } + + @AssistedFactory + interface Factory { + /** Creates a [MainThreadUnfoldTransitionProgressProvider] that wraps the [rootProvider]. */ + fun create( + rootProvider: UnfoldTransitionProgressProvider + ): MainThreadUnfoldTransitionProgressProvider + } +} diff --git a/ravenwood/README.md b/ravenwood/README.md new file mode 100644 index 000000000000..9c4fda7a50a6 --- /dev/null +++ b/ravenwood/README.md @@ -0,0 +1,28 @@ +# Ravenwood + +Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host. + +Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing. + +## Background + +Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness. + +In contrast, defining a lightweight unit testing environment mitigates these issues by running directly from build artifacts (no flashing required), runs immediately (no booting required), and runs in an isolated environment (less flakiness). + +## Guiding principles +Here’s a summary of the guiding principles for Ravenwood, aimed at addressing Robolectric design concerns and better supporting Android platform developers: + +* **API support for Ravenwood is opt-in.** Teams that own APIs decide exactly what, and how, they support their API functionality being available to tests. When an API hasn’t opted-in, the API signatures remain available for tests to compile against and/or mock, but they throw when called under a Ravenwood environment. + * _Contrasted with Robolectric which attempts to run API implementations as-is, causing maintenance pains as teams maintain or redesign their API internals._ +* **API support and customizations for Ravenwood appear directly inline with relevant code.** This improves maintenance of APIs by providing awareness of what code runs under Ravenwood, including the ability to replace code at a per-method level when Ravenwood-specific customization is needed. + * _Contrasted with Robolectric which maintains customized behavior in separate “Shadow” classes that are difficult for maintainers to be aware of._ +* **APIs supported under Ravenwood are tested to remain consistent with physical devices.** As teams progressively opt-in supporting APIs under Ravenwood, we’re requiring they bring along “bivalent” tests (such as the relevant CTS) to validate that Ravenwood behaves just like a physical device. + * _Contrasted with Robolectric, which has limited (and forked) testing of their environment, increasing their risk of accidental divergence over time and misleading “passing” signals._ +* **Ravenwood aims to support more “real” code.** As API owners progressively opt-in their code, they have the freedom to provide either a limited “fake” that is a faithful emulation of how a device behaves, or they can bring more “real” code that runs on physical devices. + * _Contrasted with Robolectric, where support for “real” code ends at the app process boundary, such as a call into `system_server`._ + +## More details + +* [Ravenwood for Test Authors](test-authors.md) +* [Ravenwood for API Maintainers](api-maintainers.md) diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md new file mode 100644 index 000000000000..30e899cd3ce3 --- /dev/null +++ b/ravenwood/api-maintainers.md @@ -0,0 +1,73 @@ +# Ravenwood for API Maintainers + +By default, Android APIs aren’t opted-in to Ravenwood, and they default to throwing when called under the Ravenwood environment. + +To opt-in to supporting an API under Ravenwood, you can use the inline annotations documented below to customize your API behavior when running under Ravenwood. Because these annotations are inline in the relevant platform source code, they serve as valuable reminders to future API maintainers of Ravenwood support expectations. + +> **Note:** to ensure that API teams are well-supported during early Ravenwood onboarding, the Ravenwood team is manually maintaining an allow-list of classes that are able to use Ravenwood annotations. Please reach out to ravenwood@ so we can offer design advice and allow-list your APIs. + +These Ravenwood-specific annotations have no bearing on the status of an API being public, `@SystemApi`, `@TestApi`, `@hide`, etc. Ravenwood annotations are an orthogonal concept that are only consumed by the internal `hoststubgen` tool during a post-processing step that generates the Ravenwood runtime environment. Teams that own APIs can continue to refactor opted-in `@hide` implementation details, as long as the test-visible behavior continues passing. + +As described in our Guiding Principles, when a team opts-in an API, we’re requiring that they bring along “bivalent” tests (such as the relevant CTS) to validate that Ravenwood behaves just like a physical device. At the moment this means adding the bivalent tests to relevant `TEST_MAPPING` files to ensure they remain consistently passing over time. These bivalent tests are important because they progressively provide the foundation on which higher-level unit tests place their trust. + +## Opt-in to supporting a single method while other methods remained opt-out + +``` +@RavenwoodKeepPartialClass +public class MyManager { + @RavenwoodKeep + public static String modeToString(int mode) { + // This method implementation runs as-is on both devices and Ravenwood + } + + public static void doComplex() { + // This method implementation runs as-is on devices, but because there + // is no method-level annotation, and the class-level default is + // “keep partial”, this method is not supported under Ravenwood and + // will throw + } +} +``` + +## Opt-in an entire class with opt-out of specific methods + +``` +@RavenwoodKeepWholeClass +public class MyStruct { + public void doSimple() { + // This method implementation runs as-is on both devices and Ravenwood, + // implicitly inheriting the class-level annotation + } + + @RavenwoodThrow + public void doComplex() { + // This method implementation runs as-is on devices, but the + // method-level annotation overrides the class-level annotation, so + // this method is not supported under Ravenwood and will throw + } +} +``` + +## Replace a complex method when under Ravenwood + +``` +@RavenwoodKeepWholeClass +public class MyStruct { + @RavenwoodReplace + public void doComplex() { + // This method implementation runs as-is on devices, but the + // implementation is replaced/substituted by the + // doComplex$ravenwood() method implementation under Ravenwood + } + + public void doComplex$ravenwood() { + // This method implementation only runs under Ravenwood + } +} +``` + +## General strategies for side-stepping tricky dependencies + +The “replace” strategy described above is quite powerful, and can be used in creative ways to sidestep tricky underlying dependencies that aren’t ready yet. + +For example, consider a constructor or static initializer that relies on unsupported functionality from another team. By factoring the unsupported logic into a dedicated method, that method can then be replaced under Ravenwood to offer baseline functionality. diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index c70c171048e7..79bfa443b330 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -1,5 +1,8 @@ # Ravenwood "policy" file for framework-minus-apex. +# Keep all AIDL interfaces +class :aidl stubclass + # Collections class android.util.ArrayMap stubclass class android.util.ArraySet stubclass diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md new file mode 100644 index 000000000000..2b5bd9083a40 --- /dev/null +++ b/ravenwood/test-authors.md @@ -0,0 +1,132 @@ +# Ravenwood for Test Authors + +The Ravenwood testing environment runs inside a single Java process on the host side, and provides a limited yet growing set of Android API functionality. + +Ravenwood explicitly does not support “large” integration tests that expect a fully booted Android OS. Instead, it’s more suited for “small” and “medium” tests where your code-under-test has been factored to remove dependencies on a fully booted device. + +When writing tests under Ravenwood, all Android API symbols associated with your declared `sdk_version` are available to link against using, but unsupported APIs will throw an exception. This design choice enables mocking of unsupported APIs, and supports sharing of test code to build “bivalent” test suites that run against either Ravenwood or a traditional device. + +## Typical test structure + +Below are the typical steps needed to add a straightforward “small” unit test: + +* Define an `android_ravenwood_test` rule in your `Android.bp` file: + +``` +android_ravenwood_test { + name: "MyTestsRavenwood", + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + ], + srcs: [ + "src/com/example/MyCode.java", + "tests/src/com/example/MyCodeTest.java", + ], + sdk_version: "test_current", + auto_gen_config: true, +} +``` + +* Write your unit test just like you would for an Android device: + +``` +@RunWith(AndroidJUnit4.class) +public class MyCodeTest { + @Test + public void testSimple() { + // ... + } +} +``` + +* APIs available under Ravenwood are stateless by default. If your test requires explicit states (such as defining the UID you’re running under, or requiring a main `Looper` thread), add a `RavenwoodRule` to declare that: + +``` +@RunWith(AndroidJUnit4.class) +public class MyCodeTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProcessApp() + .setProvideMainThread(true) + .build(); +``` + +Once you’ve defined your test, you can use typical commands to execute it locally: + +``` +$ atest MyTestsRavenwood +``` + +> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. + +You can also run your new tests automatically via `TEST_MAPPING` rules like this: + +``` +{ + "ravenwood-presubmit": [ + { + "name": "MyTestsRavenwood", + "host": true + } + ] +} +``` + +## Strategies for migration/bivalent tests + +Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can run on both a real Android device and under a Ravenwood environment. + +In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly “ignore” that test under Ravenwood, while continuing to validate that test on real devices. Please note that your test must declare a `RavenwoodRule` for the annotation to take effect. + +Test authors are encouraged to provide a `blockedBy` or `reason` argument to help future maintainers understand why a test is being ignored, and under what conditions it might be supported in the future. + +``` +@RunWith(AndroidJUnit4.class) +public class MyCodeTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testSimple() { + // Simple test that runs on both devices and Ravenwood + } + + @Test + @IgnoreUnderRavenwood(blockedBy = PackageManager.class) + public void testComplex() { + // Complex test that runs on devices, but is ignored under Ravenwood + } +} +``` + +## Strategies for unsupported APIs + +As you write tests against Ravenwood, you’ll likely discover API dependencies that aren’t supported yet. Here’s a few strategies that can help you make progress: + +* Your code-under-test may benefit from subtle dependency refactoring to reduce coupling. (For example, providing a specific `File` argument instead of deriving it internally from a `Context`.) +* Although mocking code that your team doesn’t own is a generally discouraged testing practice, it can be a valuable pressure relief valve when a dependency isn’t yet supported. + +## Strategies for debugging test development + +When writing tests you may encounter odd or hard to debug behaviors. One good place to start is at the beginning of the logs stored by atest: + +``` +$ atest MyTestsRavenwood +... +Test Logs have saved in /tmp/atest_result/20231128_094010_0e90t8v8/log +Run 'atest --history' to review test result history. +``` + +The most useful logs are in the `isolated-java-logs` text file, which can typically be tab-completed by copy-pasting the logs path mentioned in the atest output: + +``` +$ less /tmp/atest_result/20231128_133105_h9al__79/log/i*/i*/isolated-java-logs* +``` + +Here are some common known issues and recommended workarounds: + +* Some code may unconditionally interact with unsupported APIs, such as via static initializers. One strategy is to shift the logic into `@Before` methods and make it conditional by testing `RavenwoodRule.isUnderRavenwood()`. +* Some code may reference API symbols not yet present in the Ravenwood runtime, such as ART or ICU internals, or APIs from Mainline modules. One strategy is to refactor to avoid these internal dependencies, but Ravenwood aims to better support them soon. + * This may also manifest as very odd behavior, such as test not being executed at all, tracked by bug #312517322 + * This may also manifest as an obscure Mockito error claiming “Mockito can only mock non-private & non-final classes” diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 74538ac02289..6cac6a47c77b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -668,7 +668,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo final Context uiContext = displayContext.createWindowContext( TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */); magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext, - mAms.getWindowMagnificationMgr(), mAms.getTraceManager(), + mAms.getMagnificationConnectionManager(), mAms.getTraceManager(), mAms.getMagnificationController(), detectControlGestures, detectTwoFingerTripleTap, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index b5e8c849517b..2eecb4d3a86c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -158,10 +158,10 @@ import com.android.internal.util.Preconditions; import com.android.server.AccessibilityManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.accessibility.magnification.MagnificationConnectionManager; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.accessibility.magnification.MagnificationScaleProvider; -import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.policy.WindowManagerPolicy; @@ -3442,7 +3442,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && (userState.getMagnificationCapabilitiesLocked() != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) || userHasMagnificationServicesLocked(userState); - getWindowMagnificationMgr().requestConnection(connect); + getMagnificationConnectionManager().requestConnection(connect); } /** @@ -4122,17 +4122,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR_SERVICE); - getWindowMagnificationMgr().setConnection(connection); + getMagnificationConnectionManager().setConnection(connection); } /** - * Getter of {@link WindowMagnificationManager}. + * Getter of {@link MagnificationConnectionManager}. * - * @return WindowMagnificationManager + * @return MagnificationManager */ - public WindowMagnificationManager getWindowMagnificationMgr() { + public MagnificationConnectionManager getMagnificationConnectionManager() { synchronized (mLock) { - return mMagnificationController.getWindowMagnificationMgr(); + return mMagnificationController.getMagnificationConnectionManager(); } } @@ -4423,7 +4423,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.println(); } pw.append("hasWindowMagnificationConnection=").append( - String.valueOf(getWindowMagnificationMgr().isConnected())); + String.valueOf(getMagnificationConnectionManager().isConnected())); pw.println(); mMagnificationProcessor.dump(pw, getValidDisplayList()); final int userCount = mUserStates.size(); @@ -5144,7 +5144,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub for (int i = 0; i < displays.size(); i++) { final int displayId = displays.get(i).getDisplayId(); - getWindowMagnificationMgr().removeMagnificationButton(displayId); + getMagnificationConnectionManager().removeMagnificationButton(displayId); } } } @@ -5580,6 +5580,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void injectInputEventToInputFilter(InputEvent event) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS, + "injectInputEventToInputFilter"); synchronized (mLock) { final long endMillis = SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index d31b1efafcc4..e3797c98bebe 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -258,6 +258,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH public void logMagnificationTripleTap(boolean enabled) { AccessibilityStatsLogUtils.logMagnificationTripleTap(enabled); } + + @Override + public void logMagnificationTwoFingerTripleTap(boolean enabled) { + AccessibilityStatsLogUtils.logMagnificationTwoFingerTripleTap(enabled); + } }; } @@ -419,6 +424,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH /** An interface that allows testing magnification log events. */ interface MagnificationLogger { void logMagnificationTripleTap(boolean enabled); + void logMagnificationTwoFingerTripleTap(boolean enabled); } interface State { @@ -987,12 +993,14 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); - } else if (isMultiTapTriggered(3 /* taps */)) { - onTripleTap(/* up */ event); - } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) { + // Placing multiple fingers before a single finger, because achieving a + // multi finger multi tap also means achieving a single finger triple tap onTripleTap(event); + } else if (isMultiTapTriggered(3 /* taps */)) { + onTripleTap(/* up */ event); + } else if ( // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP isFingerDown() @@ -1026,6 +1034,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mCompletedTapCount++; mIsTwoFingerCountReached = false; } + + if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) { + final boolean enabled = !isActivated(); + mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled); + } return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount; } @@ -1037,6 +1050,29 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mFirstPointerDownLocation.set(Float.NaN, Float.NaN); mSecondPointerDownLocation.set(Float.NaN, Float.NaN); } + + void transitionToViewportDraggingStateAndClear(MotionEvent down) { + + if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()"); + final boolean shortcutTriggered = mShortcutTriggered; + + // Only log the 3tap and hold event + if (!shortcutTriggered) { + final boolean enabled = !isActivated(); + if (mCompletedTapCount == 2) { + // Two finger triple tap and hold + mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled); + } else { + // Triple tap and hold also belongs to triple tap event + mMagnificationLogger.logMagnificationTripleTap(enabled); + } + } + clear(); + + mViewportDraggingState.prepareForZoomInTemporary(shortcutTriggered); + zoomInTemporary(down.getX(), down.getY(), shortcutTriggered); + transitionTo(mViewportDraggingState); + } } /** @@ -1416,8 +1452,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Only log the 3tap and hold event if (!shortcutTriggered) { - // TODO:(b/309534286): Add metrics for two-finger triple-tap and fix - // the log two-finger bug before enabling the flag // Triple tap and hold also belongs to triple tap event final boolean enabled = !isActivated(); mMagnificationLogger.logMagnificationTripleTap(enabled); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java index 3ea805bbb4a6..5a3c070819bd 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java @@ -60,19 +60,19 @@ import java.lang.annotation.RetentionPolicy; import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** - * A class to manipulate window magnification through {@link MagnificationConnectionWrapper} + * A class to manipulate magnification through {@link MagnificationConnectionWrapper} * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}. * The applied magnification scale is constrained by * {@link MagnificationScaleProvider#constrainScale(float)} */ -public class WindowMagnificationManager implements +public class MagnificationConnectionManager implements PanningScalingHandler.MagnificationDelegate, WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { private static final boolean DBG = false; - private static final String TAG = "WindowMagnificationMgr"; + private static final String TAG = "MagnificationConnectionManager"; /** * Indicate that the magnification window is at the magnification center. @@ -208,7 +208,7 @@ public class WindowMagnificationManager implements private final AccessibilityTraceManager mTrace; private final MagnificationScaleProvider mScaleProvider; - public WindowMagnificationManager(Context context, Object lock, @NonNull Callback callback, + public MagnificationConnectionManager(Context context, Object lock, @NonNull Callback callback, AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) { mContext = context; mLock = lock; @@ -1040,7 +1040,7 @@ public class WindowMagnificationManager implements private float mScale = MagnificationScaleProvider.MIN_SCALE; private boolean mEnabled; - private final WindowMagnificationManager mWindowMagnificationManager; + private final MagnificationConnectionManager mMagnificationConnectionManager; // Records the bounds of window magnification. private final Rect mBounds = new Rect(); // The magnified bounds on the screen. @@ -1058,11 +1058,15 @@ public class WindowMagnificationManager implements "mTrackingTypingFocusSumTime"); private volatile long mTrackingTypingFocusSumTime = 0; - WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { + WindowMagnifier(int displayId, + MagnificationConnectionManager magnificationConnectionManager) { mDisplayId = displayId; - mWindowMagnificationManager = windowMagnificationManager; + mMagnificationConnectionManager = magnificationConnectionManager; } + // TODO(b/312324808): Investigating whether + // mMagnificationConnectionManager#enableWindowMagnificationInternal requires a sync lock + @SuppressWarnings("GuardedBy") boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, @WindowPosition int windowPosition, int id) { @@ -1072,8 +1076,8 @@ public class WindowMagnificationManager implements } final float normScale = MagnificationScaleProvider.constrainScale(scale); setMagnificationFrameOffsetRatioByWindowPosition(windowPosition); - if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale, - centerX, centerY, mMagnificationFrameOffsetRatio.x, + if (mMagnificationConnectionManager.enableWindowMagnificationInternal(mDisplayId, + normScale, centerX, centerY, mMagnificationFrameOffsetRatio.x, mMagnificationFrameOffsetRatio.y, animationCallback)) { mScale = normScale; mEnabled = true; @@ -1096,12 +1100,15 @@ public class WindowMagnificationManager implements } } + // TODO(b/312324808): Investigating whether + // mMagnificationConnectionManager#disableWindowMagnificationInternal requires a sync lock + @SuppressWarnings("GuardedBy") boolean disableWindowMagnificationInternal( @Nullable MagnificationAnimationCallback animationResultCallback) { if (!mEnabled) { return false; } - if (mWindowMagnificationManager.disableWindowMagnificationInternal( + if (mMagnificationConnectionManager.disableWindowMagnificationInternal( mDisplayId, animationResultCallback)) { mEnabled = false; mIdOfLastServiceToControl = INVALID_SERVICE_ID; @@ -1112,6 +1119,10 @@ public class WindowMagnificationManager implements return false; } + // ErrorProne says the access of mMagnificationConnectionManager#setScaleInternal should + // be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one as + // 'mLock'. Therefore, we'll put @SuppressWarnings here. + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") void setScale(float scale) { if (!mEnabled) { @@ -1119,7 +1130,8 @@ public class WindowMagnificationManager implements } final float normScale = MagnificationScaleProvider.constrainScale(scale); if (Float.compare(mScale, normScale) != 0 - && mWindowMagnificationManager.setScaleInternal(mDisplayId, scale)) { + && mMagnificationConnectionManager + .setScaleForWindowMagnificationInternal(mDisplayId, scale)) { mScale = normScale; } } @@ -1159,8 +1171,8 @@ public class WindowMagnificationManager implements } void setTrackingTypingFocusEnabled(boolean trackingTypingFocusEnabled) { - if (mWindowMagnificationManager.isWindowMagnifierEnabled(mDisplayId) - && mWindowMagnificationManager.isImeVisible(mDisplayId) + if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId) + && mMagnificationConnectionManager.isImeVisible(mDisplayId) && trackingTypingFocusEnabled) { startTrackingTypingFocusRecord(); } @@ -1206,7 +1218,7 @@ public class WindowMagnificationManager implements Slog.d(TAG, "stop and log: session duration = " + duration + ", elapsed = " + elapsed); } - mWindowMagnificationManager.logTrackingTypingFocus(duration); + mMagnificationConnectionManager.logTrackingTypingFocus(duration); mTrackingTypingFocusStartTime = 0; mTrackingTypingFocusSumTime = 0; } @@ -1216,9 +1228,14 @@ public class WindowMagnificationManager implements return mEnabled; } + // ErrorProne says the access of mMagnificationConnectionManager#moveWindowMagnifierInternal + // should be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one + // as 'mLock'. Therefore, we'll put @SuppressWarnings here. + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") void move(float offsetX, float offsetY) { - mWindowMagnificationManager.moveWindowMagnifierInternal(mDisplayId, offsetX, offsetY); + mMagnificationConnectionManager.moveWindowMagnifierInternal( + mDisplayId, offsetX, offsetY); } @GuardedBy("mLock") @@ -1270,8 +1287,10 @@ public class WindowMagnificationManager implements animationCallback); } - private boolean setScaleInternal(int displayId, float scale) { - return mConnectionWrapper != null && mConnectionWrapper.setScale(displayId, scale); + @GuardedBy("mLock") + private boolean setScaleForWindowMagnificationInternal(int displayId, float scale) { + return mConnectionWrapper != null + && mConnectionWrapper.setScaleForWindowMagnification(displayId, scale); } @GuardedBy("mLock") diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java index f0c44d64f5ec..20538f167656 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java @@ -82,16 +82,16 @@ class MagnificationConnectionWrapper { return true; } - boolean setScale(int displayId, float scale) { + boolean setScaleForWindowMagnification(int displayId, float scale) { if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";scale=" + scale); } try { - mConnection.setScale(displayId, scale); + mConnection.setScaleForWindowMagnification(displayId, scale); } catch (RemoteException e) { if (DBG) { - Slog.e(TAG, "Error calling setScale()", e); + Slog.e(TAG, "Error calling setScaleForWindowMagnification()", e); } return false; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index effd8732d086..52e123a5e70c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -77,7 +77,7 @@ import java.util.concurrent.Executor; * <b>Note</b> Updates magnification switch UI when magnification mode transition * is done and before invoking {@link TransitionCallBack#onResult}. */ -public class MagnificationController implements WindowMagnificationManager.Callback, +public class MagnificationController implements MagnificationConnectionManager.Callback, MagnificationGestureHandler.Callback, FullScreenMagnificationController.MagnificationInfoChangedCallback, WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { @@ -96,7 +96,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag; private final MagnificationScaleProvider mScaleProvider; private FullScreenMagnificationController mFullScreenMagnificationController; - private WindowMagnificationManager mWindowMagnificationMgr; + private MagnificationConnectionManager mMagnificationConnectionManager; private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; /** Whether the platform supports window magnification feature. */ private final boolean mSupportWindowMagnification; @@ -164,11 +164,11 @@ public class MagnificationController implements WindowMagnificationManager.Callb @VisibleForTesting public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, - WindowMagnificationManager windowMagnificationManager, + MagnificationConnectionManager magnificationConnectionManager, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) { this(ams, lock, context, scaleProvider, backgroundExecutor); mFullScreenMagnificationController = fullScreenMagnificationController; - mWindowMagnificationMgr = windowMagnificationManager; + mMagnificationConnectionManager = magnificationConnectionManager; } @Override @@ -179,10 +179,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (updatePersistence) { getFullScreenMagnificationController().persistScale(displayId); } - } else if (getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) { - getWindowMagnificationMgr().setScale(displayId, scale); + } else if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) { + getMagnificationConnectionManager().setScale(displayId, scale); if (updatePersistence) { - getWindowMagnificationMgr().persistScale(displayId); + getMagnificationConnectionManager().persistScale(displayId); } } } @@ -222,15 +222,15 @@ public class MagnificationController implements WindowMagnificationManager.Callb } if (showModeSwitchButton) { - getWindowMagnificationMgr().showMagnificationButton(displayId, mode); + getMagnificationConnectionManager().showMagnificationButton(displayId, mode); } else { - getWindowMagnificationMgr().removeMagnificationButton(displayId); + getMagnificationConnectionManager().removeMagnificationButton(displayId); } if (!enableSettingsPanel) { // Whether the settings panel needs to be shown is controlled in system UI. // Here, we only guarantee that the settings panel is closed when it is not needed. - getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId); + getMagnificationConnectionManager().removeMagnificationSettingsPanel(displayId); } } @@ -284,7 +284,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb final FullScreenMagnificationController screenMagnificationController = getFullScreenMagnificationController(); - final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); + final MagnificationConnectionManager magnificationConnectionManager = + getMagnificationConnectionManager(); final float scale = getTargetModeScaleFromCurrentMagnification(displayId, targetMode); final DisableMagnificationCallback animationEndCallback = new DisableMagnificationCallback(transitionCallBack, displayId, targetMode, @@ -295,7 +296,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { screenMagnificationController.reset(displayId, animationEndCallback); } else { - windowMagnificationMgr.disableWindowMagnification(displayId, false, + magnificationConnectionManager.disableWindowMagnification(displayId, false, animationEndCallback); } } @@ -340,7 +341,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb } final FullScreenMagnificationController screenMagnificationController = getFullScreenMagnificationController(); - final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); + final MagnificationConnectionManager magnificationConnectionManager = + getMagnificationConnectionManager(); final float targetScale = Float.isNaN(config.getScale()) ? getTargetModeScaleFromCurrentMagnification(displayId, targetMode) : config.getScale(); @@ -353,14 +355,15 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (targetMode == MAGNIFICATION_MODE_WINDOW) { screenMagnificationController.reset(displayId, false); if (targetActivated) { - windowMagnificationMgr.enableWindowMagnification(displayId, + magnificationConnectionManager.enableWindowMagnification(displayId, targetScale, magnificationCenter.x, magnificationCenter.y, magnificationAnimationCallback, id); } else { - windowMagnificationMgr.disableWindowMagnification(displayId, false); + magnificationConnectionManager.disableWindowMagnification(displayId, false); } } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) { - windowMagnificationMgr.disableWindowMagnification(displayId, false, null); + magnificationConnectionManager.disableWindowMagnification( + displayId, false, null); if (targetActivated) { if (!screenMagnificationController.isRegistered(displayId)) { screenMagnificationController.register(displayId); @@ -409,7 +412,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { return getFullScreenMagnificationController().getScale(displayId); } else { - return getWindowMagnificationMgr().getScale(displayId); + return getMagnificationConnectionManager().getScale(displayId); } } @@ -441,7 +444,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb mAccessibilityCallbacksDelegateArray.put(displayId, getFullScreenMagnificationController()); } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { - mAccessibilityCallbacksDelegateArray.put(displayId, getWindowMagnificationMgr()); + mAccessibilityCallbacksDelegateArray.put( + displayId, getMagnificationConnectionManager()); } else { mAccessibilityCallbacksDelegateArray.delete(displayId); } @@ -462,13 +466,13 @@ public class MagnificationController implements WindowMagnificationManager.Callb @Override public void onRequestMagnificationSpec(int displayId, int serviceId) { - final WindowMagnificationManager windowMagnificationManager; + final MagnificationConnectionManager magnificationConnectionManager; synchronized (mLock) { updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - windowMagnificationManager = mWindowMagnificationMgr; + magnificationConnectionManager = mMagnificationConnectionManager; } - if (windowMagnificationManager != null) { - mWindowMagnificationMgr.disableWindowMagnification(displayId, false); + if (magnificationConnectionManager != null) { + mMagnificationConnectionManager.disableWindowMagnification(displayId, false); } } @@ -491,7 +495,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); - scale = mWindowMagnificationMgr.getLastActivatedScale(displayId); + scale = mMagnificationConnectionManager.getLastActivatedScale(displayId); } logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale); } @@ -507,13 +511,14 @@ public class MagnificationController implements WindowMagnificationManager.Callb public void onSourceBoundsChanged(int displayId, Rect bounds) { if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) { // notify sysui the magnification scale changed on window magnifier - mWindowMagnificationMgr.onUserMagnificationScaleChanged( - mUserId, displayId, getWindowMagnificationMgr().getScale(displayId)); + mMagnificationConnectionManager.onUserMagnificationScaleChanged( + mUserId, displayId, getMagnificationConnectionManager().getScale(displayId)); final MagnificationConfig config = new MagnificationConfig.Builder() .setMode(MAGNIFICATION_MODE_WINDOW) - .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) - .setScale(getWindowMagnificationMgr().getScale(displayId)) + .setActivated( + getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) + .setScale(getMagnificationConnectionManager().getScale(displayId)) .setCenterX(bounds.exactCenterX()) .setCenterY(bounds.exactCenterY()).build(); mAms.notifyMagnificationChanged(displayId, new Region(bounds), config); @@ -525,7 +530,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb @NonNull MagnificationConfig config) { if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) { // notify sysui the magnification scale changed on fullscreen magnifier - mWindowMagnificationMgr.onUserMagnificationScaleChanged( + mMagnificationConnectionManager.onUserMagnificationScaleChanged( mUserId, displayId, config.getScale()); mAms.notifyMagnificationChanged(displayId, region, config); @@ -548,8 +553,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { final boolean fullScreenActivated = mFullScreenMagnificationController != null && mFullScreenMagnificationController.isActivated(displayId); - final boolean windowEnabled = mWindowMagnificationMgr != null - && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId); + final boolean windowEnabled = mMagnificationConnectionManager != null + && mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId); final Integer transitionMode = mTransitionModes.get(displayId); if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenActivated) || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled)) @@ -608,10 +613,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb } private void disableWindowMagnificationIfNeeded(int displayId) { - final WindowMagnificationManager windowMagnificationManager = - getWindowMagnificationMgr(); + final MagnificationConnectionManager magnificationConnectionManager = + getMagnificationConnectionManager(); if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) { - windowMagnificationManager.disableWindowMagnification(displayId, false); + magnificationConnectionManager.disableWindowMagnification(displayId, false); } } @@ -620,7 +625,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { mIsImeVisibleArray.put(displayId, shown); } - getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown); + getMagnificationConnectionManager().onImeWindowVisibilityChanged(displayId, shown); logMagnificationModeWithImeOnIfNeeded(displayId); } @@ -661,7 +666,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb /** * Updates the active user ID of {@link FullScreenMagnificationController} and {@link - * WindowMagnificationManager}. + * MagnificationConnectionManager}. * * @param userId the currently active user ID */ @@ -671,10 +676,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb } mUserId = userId; final FullScreenMagnificationController fullMagnificationController; - final WindowMagnificationManager windowMagnificationManager; + final MagnificationConnectionManager magnificationConnectionManager; synchronized (mLock) { fullMagnificationController = mFullScreenMagnificationController; - windowMagnificationManager = mWindowMagnificationMgr; + magnificationConnectionManager = mMagnificationConnectionManager; mAccessibilityCallbacksDelegateArray.clear(); mCurrentMagnificationModeArray.clear(); mLastMagnificationActivatedModeArray.clear(); @@ -685,8 +690,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (fullMagnificationController != null) { fullMagnificationController.resetAllIfNeeded(false); } - if (windowMagnificationManager != null) { - windowMagnificationManager.disableAllWindowMagnifiers(); + if (magnificationConnectionManager != null) { + magnificationConnectionManager.disableAllWindowMagnifiers(); } } @@ -700,8 +705,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (mFullScreenMagnificationController != null) { mFullScreenMagnificationController.onDisplayRemoved(displayId); } - if (mWindowMagnificationMgr != null) { - mWindowMagnificationMgr.onDisplayRemoved(displayId); + if (mMagnificationConnectionManager != null) { + mMagnificationConnectionManager.onDisplayRemoved(displayId); } mAccessibilityCallbacksDelegateArray.delete(displayId); mCurrentMagnificationModeArray.delete(displayId); @@ -728,7 +733,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb * @param enabled Enable the following typing focus feature */ public void setMagnificationFollowTypingEnabled(boolean enabled) { - getWindowMagnificationMgr().setMagnificationFollowTypingEnabled(enabled); + getMagnificationConnectionManager().setMagnificationFollowTypingEnabled(enabled); getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled); } @@ -805,29 +810,29 @@ public class MagnificationController implements WindowMagnificationManager.Callb } /** - * Getter of {@link WindowMagnificationManager}. + * Getter of {@link MagnificationConnectionManager}. * - * @return {@link WindowMagnificationManager}. + * @return {@link MagnificationConnectionManager}. */ - public WindowMagnificationManager getWindowMagnificationMgr() { + public MagnificationConnectionManager getMagnificationConnectionManager() { synchronized (mLock) { - if (mWindowMagnificationMgr == null) { - mWindowMagnificationMgr = new WindowMagnificationManager(mContext, + if (mMagnificationConnectionManager == null) { + mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, mLock, this, mAms.getTraceManager(), mScaleProvider); } - return mWindowMagnificationMgr; + return mMagnificationConnectionManager; } } private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) { if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { - if (mWindowMagnificationMgr == null - || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) { + if (mMagnificationConnectionManager == null + || !mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId)) { return null; } - mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId), - mWindowMagnificationMgr.getCenterY(displayId)); + mTempPoint.set(mMagnificationConnectionManager.getCenterX(displayId), + mMagnificationConnectionManager.getCenterY(displayId)); } else { if (mFullScreenMagnificationController == null || !mFullScreenMagnificationController.isActivated(displayId)) { @@ -858,10 +863,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb } } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { synchronized (mLock) { - if (mWindowMagnificationMgr == null) { + if (mMagnificationConnectionManager == null) { return false; } - isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId); + isActivated = mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId); } } return isActivated; @@ -980,7 +985,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb mCurrentCenter.x, mCurrentCenter.y, mAnimate, MAGNIFICATION_GESTURE_HANDLER_ID); } else { - getWindowMagnificationMgr().enableWindowMagnification(mDisplayId, + getMagnificationConnectionManager().enableWindowMagnification(mDisplayId, mCurrentScale, mCurrentCenter.x, mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null, MAGNIFICATION_GESTURE_HANDLER_ID); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java index 5cf2a638fa3e..ed8f1ab3a1b2 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -84,13 +84,13 @@ public class MagnificationProcessor { .setCenterX(fullScreenMagnificationController.getCenterX(displayId)) .setCenterY(fullScreenMagnificationController.getCenterY(displayId)); } else if (mode == MAGNIFICATION_MODE_WINDOW) { - final WindowMagnificationManager windowMagnificationManager = - mController.getWindowMagnificationMgr(); + final MagnificationConnectionManager magnificationConnectionManager = + mController.getMagnificationConnectionManager(); builder.setMode(mode) .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW)) - .setScale(windowMagnificationManager.getScale(displayId)) - .setCenterX(windowMagnificationManager.getCenterX(displayId)) - .setCenterY(windowMagnificationManager.getCenterY(displayId)); + .setScale(magnificationConnectionManager.getScale(displayId)) + .setCenterX(magnificationConnectionManager.getCenterX(displayId)) + .setCenterY(magnificationConnectionManager.getCenterY(displayId)); } else { // For undefined mode, set enabled to false builder.setActivated(false); @@ -135,12 +135,12 @@ public class MagnificationProcessor { } } else if (configMode == MAGNIFICATION_MODE_WINDOW) { if (configActivated) { - return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId, - config.getScale(), config.getCenterX(), config.getCenterY(), + return mController.getMagnificationConnectionManager().enableWindowMagnification( + displayId, config.getScale(), config.getCenterX(), config.getCenterY(), animate ? STUB_ANIMATION_CALLBACK : null, id); } else { - return mController.getWindowMagnificationMgr() + return mController.getMagnificationConnectionManager() .disableWindowMagnification(displayId, false); } } @@ -256,7 +256,7 @@ public class MagnificationProcessor { if (currentMode == MAGNIFICATION_MODE_FULLSCREEN) { getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification); } else if (currentMode == MAGNIFICATION_MODE_WINDOW) { - mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId, + mController.getMagnificationConnectionManager().getMagnificationSourceBounds(displayId, outRegion); } } @@ -297,8 +297,8 @@ public class MagnificationProcessor { if (mode == MAGNIFICATION_MODE_FULLSCREEN) { return mController.getFullScreenMagnificationController().reset(displayId, animate); } else if (mode == MAGNIFICATION_MODE_WINDOW) { - return mController.getWindowMagnificationMgr().disableWindowMagnification(displayId, - false, animate ? STUB_ANIMATION_CALLBACK : null); + return mController.getMagnificationConnectionManager().disableWindowMagnification( + displayId, false, animate ? STUB_ANIMATION_CALLBACK : null); } return false; } @@ -325,19 +325,20 @@ public class MagnificationProcessor { */ public void resetAllIfNeeded(int connectionId) { mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId); - mController.getWindowMagnificationMgr().resetAllIfNeeded(connectionId); + mController.getMagnificationConnectionManager().resetAllIfNeeded(connectionId); } /** * {@link FullScreenMagnificationController#isActivated(int)} - * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)} + * {@link MagnificationConnectionManager#isWindowMagnifierEnabled(int)} */ public boolean isMagnifying(int displayId) { int mode = getControllingMode(displayId); if (mode == MAGNIFICATION_MODE_FULLSCREEN) { return mController.getFullScreenMagnificationController().isActivated(displayId); } else if (mode == MAGNIFICATION_MODE_WINDOW) { - return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId); + return mController.getMagnificationConnectionManager().isWindowMagnifierEnabled( + displayId); } return false; } @@ -416,22 +417,23 @@ public class MagnificationProcessor { pw.append(" SupportWindowMagnification=" + mController.supportWindowMagnification()).println(); pw.append(" WindowMagnificationConnectionState=" - + mController.getWindowMagnificationMgr().getConnectionState()).println(); + + mController.getMagnificationConnectionManager().getConnectionState()).println(); } private int getIdOfLastServiceToMagnify(int mode, int displayId) { return (mode == MAGNIFICATION_MODE_FULLSCREEN) ? mController.getFullScreenMagnificationController() .getIdOfLastServiceToMagnify(displayId) - : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify( + : mController.getMagnificationConnectionManager().getIdOfLastServiceToMagnify( displayId); } private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId, int mode) { if (mode == MAGNIFICATION_MODE_WINDOW) { - pw.append(" TrackingTypingFocusEnabled=" + mController - .getWindowMagnificationMgr().isTrackingTypingFocusEnabled(displayId)) + pw.append(" TrackingTypingFocusEnabled=" + + mController.getMagnificationConnectionManager() + .isTrackingTypingFocusEnabled(displayId)) .println(); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java index 94556282d0d3..6b48d2bacf9d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java @@ -45,6 +45,7 @@ class PanningScalingHandler extends private static final String TAG = "PanningScalingHandler"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + // TODO(b/312372035): Revisit the scope of usage of the interface interface MagnificationDelegate { boolean processScroll(int displayId, float distanceX, float distanceY); void setScale(int displayId, float scale); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 36e751181cfd..73c267a6e262 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -79,7 +79,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl private static final float MIN_SCALE = 1.0f; private static final float MAX_SCALE = MagnificationScaleProvider.MAX_SCALE; - private final WindowMagnificationManager mWindowMagnificationMgr; + private final MagnificationConnectionManager mMagnificationConnectionManager; @VisibleForTesting final DelegatingState mDelegatingState; @VisibleForTesting @@ -101,7 +101,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl private long mTripleTapAndHoldStartedTime = 0; public WindowMagnificationGestureHandler(@UiContext Context context, - WindowMagnificationManager windowMagnificationMgr, + MagnificationConnectionManager magnificationConnectionManager, AccessibilityTraceManager trace, Callback callback, boolean detectSingleFingerTripleTap, @@ -115,7 +115,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl "WindowMagnificationGestureHandler() , displayId = " + displayId + ")"); } mContext = context; - mWindowMagnificationMgr = windowMagnificationMgr; + mMagnificationConnectionManager = magnificationConnectionManager; mMotionEventDispatcherDelegate = new MotionEventDispatcherDelegate(context, (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent, policyFlags)); @@ -128,18 +128,18 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public boolean processScroll(int displayId, float distanceX, float distanceY) { - return mWindowMagnificationMgr.processScroll(displayId, distanceX, - distanceY); + return mMagnificationConnectionManager.processScroll( + displayId, distanceX, distanceY); } @Override public void setScale(int displayId, float scale) { - mWindowMagnificationMgr.setScale(displayId, scale); + mMagnificationConnectionManager.setScale(displayId, scale); } @Override public float getScale(int displayId) { - return mWindowMagnificationMgr.getScale(displayId); + return mMagnificationConnectionManager.getScale(displayId); } })); @@ -167,7 +167,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl Slog.i(mLogTag, "onDestroy(); delayed = " + mDetectingState.toString()); } - mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, true); + mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, true); resetToDetectState(); } @@ -176,7 +176,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl final Point screenSize = mTempPoint; getScreenSize(mTempPoint); toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f, - WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER); } private void getScreenSize(Point outSize) { @@ -190,28 +190,29 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl } private void enableWindowMagnifier(float centerX, float centerY, - @WindowMagnificationManager.WindowPosition int windowPosition) { + @MagnificationConnectionManager.WindowPosition int windowPosition) { if (DEBUG_ALL) { Slog.i(mLogTag, "enableWindowMagnifier :" + centerX + ", " + centerY + ", " + windowPosition); } final float scale = MathUtils.constrain( - mWindowMagnificationMgr.getPersistedScale(mDisplayId), MIN_SCALE, MAX_SCALE); - mWindowMagnificationMgr.enableWindowMagnification(mDisplayId, scale, centerX, centerY, - windowPosition); + mMagnificationConnectionManager.getPersistedScale(mDisplayId), + MIN_SCALE, MAX_SCALE); + mMagnificationConnectionManager.enableWindowMagnification( + mDisplayId, scale, centerX, centerY, windowPosition); } private void disableWindowMagnifier() { if (DEBUG_ALL) { Slog.i(mLogTag, "disableWindowMagnifier()"); } - mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, false); + mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, false); } private void toggleMagnification(float centerX, float centerY, - @WindowMagnificationManager.WindowPosition int windowPosition) { - if (mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)) { + @MagnificationConnectionManager.WindowPosition int windowPosition) { + if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId)) { disableWindowMagnifier(); } else { enableWindowMagnifier(centerX, centerY, windowPosition); @@ -223,7 +224,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl Slog.i(mLogTag, "onTripleTap()"); } toggleMagnification(up.getX(), up.getY(), - WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER); } @VisibleForTesting @@ -232,9 +233,9 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl Slog.i(mLogTag, "onTripleTapAndHold()"); } mViewportDraggingState.mEnabledBeforeDrag = - mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId); + mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId); enableWindowMagnifier(up.getX(), up.getY(), - WindowMagnificationManager.WINDOW_POSITION_AT_TOP_LEFT); + MagnificationConnectionManager.WINDOW_POSITION_AT_TOP_LEFT); mTripleTapAndHoldStartedTime = SystemClock.uptimeMillis(); transitionTo(mViewportDraggingState); } @@ -242,7 +243,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @VisibleForTesting void releaseTripleTapAndHold() { if (!mViewportDraggingState.mEnabledBeforeDrag) { - mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, true); + mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, true); } transitionTo(mDetectingState); if (mTripleTapAndHoldStartedTime != 0) { @@ -331,7 +332,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public void onExit() { mPanningScalingHandler.setEnabled(false); - mWindowMagnificationMgr.persistScale(mDisplayId); + mMagnificationConnectionManager.persistScale(mDisplayId); clear(); } @@ -404,7 +405,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl if (!Float.isNaN(mLastX) && !Float.isNaN(mLastY)) { float offsetX = event.getX() - mLastX; float offsetY = event.getY() - mLastY; - mWindowMagnificationMgr.moveWindowMagnification(mDisplayId, offsetX, + mMagnificationConnectionManager.moveWindowMagnification(mDisplayId, offsetX, offsetY); } mLastX = event.getX(); @@ -522,7 +523,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public boolean shouldStopDetection(MotionEvent motionEvent) { - return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId) + return !mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId) && !mDetectSingleFingerTripleTap && !(mDetectTwoFingerTripleTap && Flags.enableMagnificationMultipleFingerMultipleTapGesture()); @@ -540,7 +541,8 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl "onGestureDetected : delayedEventQueue = " + delayedEventQueue); } if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE - && mWindowMagnificationMgr.pointersInWindow(mDisplayId, motionEvent) > 0) { + && mMagnificationConnectionManager + .pointersInWindow(mDisplayId, motionEvent) > 0) { transitionTo(mObservePanningScalingState); } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) { onTripleTap(motionEvent); @@ -584,7 +586,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl + ", mMagnifiedInteractionState=" + mObservePanningScalingState + ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) - + ", mWindowMagnificationMgr=" + mWindowMagnificationMgr + + ", mMagnificationConnectionManager=" + mMagnificationConnectionManager + ", mDisplayId=" + mDisplayId + '}'; } diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index 3583a7816ab2..a159a5e003c9 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -55,5 +55,15 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsVirtualDevicesCameraTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } ] } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index e6bfeb79fafb..45d7314f3fab 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -39,7 +39,6 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; -import android.app.compat.CompatChanges; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -55,8 +54,6 @@ import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; @@ -81,7 +78,6 @@ import android.hardware.input.VirtualNavigationTouchpadConfig; import android.hardware.input.VirtualTouchEvent; import android.hardware.input.VirtualTouchscreenConfig; import android.os.Binder; -import android.os.Build; import android.os.IBinder; import android.os.LocaleList; import android.os.Looper; @@ -122,22 +118,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; - /** - * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent - * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow - * for the creation of private, auto-mirror, and fixed orientation displays since - * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. - * - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - public static final long MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER = - 294837146L; - private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL @@ -365,8 +345,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; - if (!CompatChanges.isChangeEnabled( - MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER, mOwnerUid)) { + if (!Flags.consistentDisplayFlags()) { flags |= DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC; } if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { @@ -960,7 +939,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub if (mVirtualCameraController == null) { throw new UnsupportedOperationException("Virtual camera controller is not available"); } - mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig)); + mVirtualCameraController.registerCamera(cameraConfig); } @Override // Binder call @@ -972,7 +951,19 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub if (mVirtualCameraController == null) { throw new UnsupportedOperationException("Virtual camera controller is not available"); } - mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig)); + mVirtualCameraController.unregisterCamera(cameraConfig); + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public int getVirtualCameraId(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { + super.getVirtualCameraId_enforcePermission(); + Objects.requireNonNull(cameraConfig); + if (mVirtualCameraController == null) { + throw new UnsupportedOperationException("Virtual camera controller is not available"); + } + return mVirtualCameraController.getCameraId(cameraConfig); } @Override diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index d194bb29b19b..9b78ed41206d 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -460,11 +460,11 @@ public class VirtualDeviceManagerService extends SystemService { synchronized (mVirtualDeviceManagerLock) { if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) { - final long callindId = Binder.clearCallingIdentity(); + final long callingId = Binder.clearCallingIdentity(); try { registerCdmAssociationListener(); } finally { - Binder.restoreCallingIdentity(callindId); + Binder.restoreCallingIdentity(callingId); } } mVirtualDevices.put(deviceId, virtualDevice); @@ -498,13 +498,16 @@ public class VirtualDeviceManagerService extends SystemService { synchronized (mVirtualDeviceManagerLock) { virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId()); if (virtualDeviceImpl == null) { - throw new SecurityException("Invalid VirtualDevice"); + throw new SecurityException( + "Invalid VirtualDevice (deviceId = " + virtualDevice.getDeviceId() + + ")"); } } if (virtualDeviceImpl.getOwnerUid() != callingUid) { throw new SecurityException( "uid " + callingUid - + " is not the owner of the supplied VirtualDevice"); + + " is not the owner of the supplied VirtualDevice (deviceId = " + + virtualDevice.getDeviceId() + ")"); } return virtualDeviceImpl.createVirtualDisplay( @@ -851,6 +854,11 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public int getDeviceIdForDisplayId(int displayId) { + return mImpl.getDeviceIdForDisplayId(displayId); + } + + @Override public @Nullable String getPersistentIdForDevice(int deviceId) { if (deviceId == Context.DEVICE_ID_DEFAULT) { return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java index 06be3f39dcd1..d089b05238e4 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java @@ -27,14 +27,14 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.ArraySet; +import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; -import java.util.Set; +import java.util.Map; /** * Manages the registration and removal of virtual camera from the server side. @@ -47,10 +47,13 @@ public final class VirtualCameraController implements IBinder.DeathRecipient { private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera"; private static final String TAG = "VirtualCameraController"; + private final Object mServiceLock = new Object(); + + @GuardedBy("mServiceLock") @Nullable private IVirtualCameraService mVirtualCameraService; @GuardedBy("mCameras") - private final Set<VirtualCameraConfig> mCameras = new ArraySet<>(); + private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>(); public VirtualCameraController() { connectVirtualCameraService(); @@ -67,19 +70,16 @@ public final class VirtualCameraController implements IBinder.DeathRecipient { * @param cameraConfig The {@link VirtualCameraConfig} sent by the client. */ public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) { - // Try to connect to service if not connected already. - if (mVirtualCameraService == null) { - connectVirtualCameraService(); - } - // Throw exception if we are unable to connect to service. - if (mVirtualCameraService == null) { - throw new IllegalStateException("Virtual camera service is not connected."); - } + connectVirtualCameraServiceIfNeeded(); try { if (registerCameraWithService(cameraConfig)) { + CameraDescriptor cameraDescriptor = + new CameraDescriptor(cameraConfig); + IBinder binder = cameraConfig.getCallback().asBinder(); + binder.linkToDeath(cameraDescriptor, 0 /* flags */); synchronized (mCameras) { - mCameras.add(cameraConfig); + mCameras.put(binder, cameraDescriptor); } } else { // TODO(b/310857519): Revisit this to find a better way of indicating failure. @@ -96,24 +96,44 @@ public final class VirtualCameraController implements IBinder.DeathRecipient { * @param cameraConfig The {@link VirtualCameraConfig} sent by the client. */ public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) { - try { - if (mVirtualCameraService == null) { - Slog.w(TAG, "Virtual camera service is not connected."); + synchronized (mCameras) { + IBinder binder = cameraConfig.getCallback().asBinder(); + if (!mCameras.containsKey(binder)) { + Slog.w(TAG, "Virtual camera was not registered."); } else { - mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder()); + connectVirtualCameraServiceIfNeeded(); + + try { + synchronized (mServiceLock) { + mVirtualCameraService.unregisterCamera(binder); + } + mCameras.remove(binder); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } - synchronized (mCameras) { - mCameras.remove(cameraConfig); + } + } + + /** Return the id of the virtual camera with the given config. */ + public int getCameraId(@NonNull VirtualCameraConfig cameraConfig) { + connectVirtualCameraServiceIfNeeded(); + + try { + synchronized (mServiceLock) { + return mVirtualCameraService.getCameraId(cameraConfig.getCallback().asBinder()); } } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @Override public void binderDied() { Slog.d(TAG, "Virtual camera service died."); - mVirtualCameraService = null; + synchronized (mServiceLock) { + mVirtualCameraService = null; + } synchronized (mCameras) { mCameras.clear(); } @@ -122,32 +142,48 @@ public final class VirtualCameraController implements IBinder.DeathRecipient { /** Release resources associated with this controller. */ public void close() { synchronized (mCameras) { - if (mVirtualCameraService == null) { - Slog.w(TAG, "Virtual camera service is not connected."); - } else { - for (VirtualCameraConfig config : mCameras) { - try { - mVirtualCameraService.unregisterCamera(config.getCallback().asBinder()); - } catch (RemoteException e) { - Slog.w(TAG, "close(): Camera failed to be removed on camera " - + "service.", e); + if (!mCameras.isEmpty()) { + connectVirtualCameraServiceIfNeeded(); + + synchronized (mServiceLock) { + for (IBinder binder : mCameras.keySet()) { + try { + mVirtualCameraService.unregisterCamera(binder); + } catch (RemoteException e) { + Slog.w(TAG, "close(): Camera failed to be removed on camera " + + "service.", e); + } } } + mCameras.clear(); } - mCameras.clear(); } - mVirtualCameraService = null; + synchronized (mServiceLock) { + mVirtualCameraService = null; + } } /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */ public void dump(PrintWriter fout, String indent) { fout.println(indent + "VirtualCameraController:"); indent += indent; - fout.printf("%sService:%s\n", indent, mVirtualCameraService); synchronized (mCameras) { fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size()); - for (VirtualCameraConfig config : mCameras) { - fout.printf("%s token: %s\n", indent, config); + for (CameraDescriptor descriptor : mCameras.values()) { + fout.printf("%s token: %s\n", indent, descriptor.mConfig); + } + } + } + + private void connectVirtualCameraServiceIfNeeded() { + synchronized (mServiceLock) { + // Try to connect to service if not connected already. + if (mVirtualCameraService == null) { + connectVirtualCameraService(); + } + // Throw exception if we are unable to connect to service. + if (mVirtualCameraService == null) { + throw new IllegalStateException("Virtual camera service is not connected."); } } } @@ -173,7 +209,24 @@ public final class VirtualCameraController implements IBinder.DeathRecipient { private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException { VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config); - return mVirtualCameraService.registerCamera(config.getCallback().asBinder(), - serviceConfiguration); + synchronized (mServiceLock) { + return mVirtualCameraService.registerCamera(config.getCallback().asBinder(), + serviceConfiguration); + } + } + + private final class CameraDescriptor implements IBinder.DeathRecipient { + + private final VirtualCameraConfig mConfig; + + CameraDescriptor(VirtualCameraConfig config) { + mConfig = config; + } + + @Override + public void binderDied() { + Slog.d(TAG, "Virtual camera binder died"); + unregisterCamera(mConfig); + } } } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 77b6d583808c..eb3ec2444210 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -1466,8 +1466,11 @@ public class BinaryTransparencyService extends SystemService { if (android.security.Flags.binaryTransparencySepolicyHash()) { byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes( "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer()); - String sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false); - Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded); + String sepolicyHashEncoded = null; + if (sepolicyHash != null) { + sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false); + Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded); + } FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED, sepolicyHashEncoded, mVbmetaDigest); } diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index 987507fe7f03..c258370dc12b 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -1,5 +1,5 @@ -# BootReceiver -per-file BootReceiver.java = gaillard@google.com +# BootReceiver / Watchdog +per-file BootReceiver.java,Watchdog.java = gaillard@google.com # Connectivity / Networking per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java,VpnManagerService.java = file:/services/core/java/com/android/server/net/OWNERS @@ -35,7 +35,7 @@ per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo per-file MmsServiceBroker.java = file:/telephony/OWNERS per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS -per-file PinnerService.java = file:/apct-tests/perftests/OWNERS +per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 0e7b4aa32b44..c5c2b0b5dd59 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2023 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. @@ -20,6 +20,9 @@ import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; import static android.app.ActivityManager.UID_OBSERVER_GONE; import static android.os.Process.SYSTEM_UID; +import static com.android.server.flags.Flags.pinWebview; + +import android.annotation.EnforcePermission; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +31,8 @@ import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.app.SearchManager; import android.app.UidObserver; +import android.app.pinner.IPinnerService; +import android.app.pinner.PinnedFileStat; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -48,6 +53,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -83,6 +89,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -111,16 +119,15 @@ public final class PinnerService extends SystemService { private static final int KEY_ASSISTANT = 2; // Pin using pinlist.meta when pinning apps. - private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean( - "pinner.use_pinlist", true); - // Pin the whole odex/vdex/etc file when pinning apps. - private static boolean PROP_PIN_ODEX = SystemProperties.getBoolean( - "pinner.whole_odex", true); + private static boolean PROP_PIN_PINLIST = + SystemProperties.getBoolean("pinner.use_pinlist", true); private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app. private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app. + public static final String ANON_REGION_STAT_NAME = "[anon]"; + @IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT}) @Retention(RetentionPolicy.SOURCE) public @interface AppKey {} @@ -135,8 +142,7 @@ public final class PinnerService extends SystemService { private SearchManager mSearchManager; /** The list of the statically pinned files. */ - @GuardedBy("this") - private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>(); + @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>(); /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */ @GuardedBy("this") @@ -175,6 +181,7 @@ public final class PinnerService extends SystemService { private final boolean mConfiguredToPinCamera; private final boolean mConfiguredToPinHome; private final boolean mConfiguredToPinAssistant; + private final int mConfiguredWebviewPinBytes; private BinderService mBinderService; private PinnerHandler mPinnerHandler = null; @@ -214,6 +221,11 @@ public final class PinnerService extends SystemService { protected void publishBinderService(PinnerService service, Binder binderService) { service.publishBinderService("pinner", binderService); } + + protected PinnedFile pinFileInternal(String fileToPin, + int maxBytesToPin, boolean attemptPinIntrospection) { + return PinnerService.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection); + } } public PinnerService(Context context) { @@ -233,6 +245,8 @@ public final class PinnerService extends SystemService { com.android.internal.R.bool.config_pinnerHomeApp); mConfiguredToPinAssistant = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerAssistantApp); + mConfiguredWebviewPinBytes = context.getResources().getInteger( + com.android.internal.R.integer.config_pinnerWebviewPinBytes); mPinKeys = createPinKeys(); mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper()); @@ -322,7 +336,7 @@ public final class PinnerService extends SystemService { public List<PinnedFileStats> dumpDataForStatsd() { List<PinnedFileStats> pinnedFileStats = new ArrayList<>(); synchronized (PinnerService.this) { - for (PinnedFile pinnedFile : mPinnedFiles) { + for (PinnedFile pinnedFile : mPinnedFiles.values()) { pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile)); } @@ -358,39 +372,17 @@ public final class PinnerService extends SystemService { com.android.internal.R.array.config_defaultPinnerServiceFiles); // Continue trying to pin each file even if we fail to pin some of them for (String fileToPin : filesToPin) { - PinnedFile pf = pinFile(fileToPin, - Integer.MAX_VALUE, - /*attemptPinIntrospection=*/false); + PinnedFile pf = mInjector.pinFileInternal(fileToPin, Integer.MAX_VALUE, + /*attemptPinIntrospection=*/false); if (pf == null) { Slog.e(TAG, "Failed to pin file = " + fileToPin); continue; } synchronized (this) { - mPinnedFiles.add(pf); - } - if (fileToPin.endsWith(".jar") | fileToPin.endsWith(".apk")) { - // Check whether the runtime has compilation artifacts to pin. - String arch = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); - String[] files = null; - try { - files = DexFile.getDexFileOutputPaths(fileToPin, arch); - } catch (IOException ioe) { } - if (files == null) { - continue; - } - for (String file : files) { - PinnedFile df = pinFile(file, - Integer.MAX_VALUE, - /*attemptPinIntrospection=*/false); - if (df == null) { - Slog.i(TAG, "Failed to pin ART file = " + file); - continue; - } - synchronized (this) { - mPinnedFiles.add(df); - } - } + mPinnedFiles.put(pf.fileName, pf); } + pf.groupName = "system"; + pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, null); } refreshPinAnonConfig(); @@ -487,7 +479,7 @@ public final class PinnerService extends SystemService { pinnedAppFiles = new ArrayList<>(app.mFiles); } for (PinnedFile pinnedFile : pinnedAppFiles) { - pinnedFile.close(); + unpinFile(pinnedFile.fileName); } } @@ -495,6 +487,19 @@ public final class PinnerService extends SystemService { return ResolverActivity.class.getName().equals(info.name); } + public int getWebviewPinQuota() { + if (!pinWebview()) { + return 0; + } + int quota = mConfiguredWebviewPinBytes; + int overrideQuota = SystemProperties.getInt("pinner.pin_webview_size", -1); + if (overrideQuota != -1) { + // Quota was overridden + quota = overrideQuota; + } + return quota; + } + private ApplicationInfo getCameraInfo(int userHandle) { Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle, @@ -728,7 +733,7 @@ public final class PinnerService extends SystemService { case KEY_ASSISTANT: return "Assistant"; default: - return null; + return ""; } } @@ -868,11 +873,12 @@ public final class PinnerService extends SystemService { continue; } - PinnedFile pf = pinFile(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true); + PinnedFile pf = mInjector.pinFileInternal(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true); if (pf == null) { Slog.e(TAG, "Failed to pin " + apk); continue; } + pf.groupName = getNameForKey(key); if (DEBUG) { Slog.i(TAG, "Pinned " + pf.fileName); @@ -882,40 +888,118 @@ public final class PinnerService extends SystemService { } apkPinSizeLimit -= pf.bytesPinned; + if (apk.equals(appInfo.sourceDir)) { + pinOptimizedDexDependencies(pf, apkPinSizeLimit, appInfo); + } } + } - // determine the ABI from either ApplicationInfo or Build - String abi = appInfo.primaryCpuAbi != null ? appInfo.primaryCpuAbi : - Build.SUPPORTED_ABIS[0]; - String arch = VMRuntime.getInstructionSet(abi); - // get the path to the odex or oat file - String baseCodePath = appInfo.getBaseCodePath(); - String[] files = null; - try { - files = DexFile.getDexFileOutputPaths(baseCodePath, arch); - } catch (IOException ioe) {} - if (files == null) { - return; + /** + * Pin file or apk to memory. + * + * Prefer to use this method instead of {@link #pinFileInternal(String, int, boolean)} as it + * takes care of accounting and if pinning an apk, it also pins any extra optimized art files + * that related to the file but not within itself. + * + * @param fileToPin File to pin + * @param maxBytesToPin maximum quota allowed for pinning + * @return total bytes that were pinned. + */ + public int pinFile(String fileToPin, int maxBytesToPin, @Nullable ApplicationInfo appInfo, + @Nullable String groupName) { + PinnedFile existingPin; + synchronized(this) { + existingPin = mPinnedFiles.get(fileToPin); + } + if (existingPin != null) { + if (existingPin.bytesPinned == maxBytesToPin) { + // Duplicate pin requesting same amount of bytes, lets just bail out. + return 0; + } else { + // User decided to pin a different amount of bytes than currently pinned + // so this is a valid pin request. Unpin the previous version before repining. + if (DEBUG) { + Slog.d(TAG, "Unpinning file prior to repin: " + fileToPin); + } + unpinFile(fileToPin); + } } - //not pinning the oat/odex is not a fatal error - for (String file : files) { - PinnedFile pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false); - if (pf != null) { - synchronized (this) { - if (PROP_PIN_ODEX) { - pinnedApp.mFiles.add(pf); - } + boolean isApk = fileToPin.endsWith(".apk"); + int bytesPinned = 0; + PinnedFile pf = mInjector.pinFileInternal(fileToPin, maxBytesToPin, + /*attemptPinIntrospection=*/isApk); + if (pf == null) { + Slog.e(TAG, "Failed to pin file = " + fileToPin); + return 0; + } + pf.groupName = groupName != null ? groupName : ""; + + maxBytesToPin -= bytesPinned; + bytesPinned += pf.bytesPinned; + + synchronized (this) { + mPinnedFiles.put(pf.fileName, pf); + } + if (maxBytesToPin > 0) { + pinOptimizedDexDependencies(pf, maxBytesToPin, appInfo); + } + return bytesPinned; + } + + /** + * Pin any dependency optimized files generated by ART. + * @param pinnedFile An already pinned file whose dependencies we want pinned. + * @param maxBytesToPin Maximum amount of bytes to pin. + * @param appInfo Used to determine the ABI in case the application has one custom set, when set + * to null it will use the default supported ABI by the device. + * @return total bytes pinned. + */ + private int pinOptimizedDexDependencies( + PinnedFile pinnedFile, int maxBytesToPin, @Nullable ApplicationInfo appInfo) { + if (pinnedFile == null) { + return 0; + } + + int bytesPinned = 0; + if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) { + String abi = null; + if (appInfo != null) { + abi = appInfo.primaryCpuAbi; + } + if (abi == null) { + abi = Build.SUPPORTED_ABIS[0]; + } + // Check whether the runtime has compilation artifacts to pin. + String arch = VMRuntime.getInstructionSet(abi); + String[] files = null; + try { + files = DexFile.getDexFileOutputPaths(pinnedFile.fileName, arch); + } catch (IOException ioe) { + } + if (files == null) { + return bytesPinned; + } + for (String file : files) { + // Unpin if it was already pinned prior to re-pinning. + unpinFile(file); + + PinnedFile df = mInjector.pinFileInternal(file, Integer.MAX_VALUE, + /*attemptPinIntrospection=*/false); + if (df == null) { + Slog.i(TAG, "Failed to pin ART file = " + file); + return bytesPinned; } - if (DEBUG) { - if (PROP_PIN_ODEX) { - Slog.i(TAG, "Pinned " + pf.fileName); - } else { - Slog.i(TAG, "Pinned [skip] " + pf.fileName); - } + df.groupName = pinnedFile.groupName; + pinnedFile.pinnedDeps.add(df); + maxBytesToPin -= df.bytesPinned; + bytesPinned += df.bytesPinned; + synchronized (this) { + mPinnedFiles.put(df.fileName, df); } } } + return bytesPinned; } /** mlock length bytes of fileToPin in memory @@ -955,9 +1039,11 @@ public final class PinnerService extends SystemService { * zip in order to extract the * @return Pinned memory resource owner thing or null on error */ - private static PinnedFile pinFile(String fileToPin, - int maxBytesToPin, - boolean attemptPinIntrospection) { + private static PinnedFile pinFileInternal( + String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) { + if (DEBUG) { + Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection); + } ZipFile fileAsZip = null; InputStream pinRangeStream = null; try { @@ -968,13 +1054,15 @@ public final class PinnerService extends SystemService { if (fileAsZip != null) { pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin); } - - Slog.d(TAG, "pinRangeStream: " + pinRangeStream); - - PinRangeSource pinRangeSource = (pinRangeStream != null) - ? new PinRangeSourceStream(pinRangeStream) - : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); - return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); + boolean use_pinlist = (pinRangeStream != null); + PinRangeSource pinRangeSource = use_pinlist + ? new PinRangeSourceStream(pinRangeStream) + : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); + PinnedFile pinnedFile = pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); + if (pinnedFile != null) { + pinnedFile.used_pinlist = use_pinlist; + } + return pinnedFile; } finally { safeClose(pinRangeStream); safeClose(fileAsZip); // Also closes any streams we've opened @@ -1013,9 +1101,23 @@ public final class PinnerService extends SystemService { return null; } + // Looking at root directory is the old behavior but still some apps rely on it so keeping + // for backward compatibility. As doing a single item lookup is cheap in the root. ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME); + + if (pinMetaEntry == null) { + // It is usually within an apk's control to include files in assets/ directory + // so this would be the expected point to have the pinlist.meta coming from. + // we explicitly avoid doing an exhaustive search because it may be expensive so + // prefer to have a good known location to retrieve the file. + pinMetaEntry = zipFile.getEntry("assets/" + PIN_META_FILENAME); + } + InputStream pinMetaStream = null; if (pinMetaEntry != null) { + if (DEBUG) { + Slog.d(TAG, "Found pinlist.meta for " + fileName); + } try { pinMetaStream = zipFile.getInputStream(pinMetaEntry); } catch (IOException ex) { @@ -1024,6 +1126,10 @@ public final class PinnerService extends SystemService { fileName), ex); } + } else { + Slog.w(TAG, + String.format( + "Could not find pinlist.meta for \"%s\": pinning as blob", fileName)); } return pinMetaStream; } @@ -1160,6 +1266,49 @@ public final class PinnerService extends SystemService { } } } + private List<PinnedFile> getAllPinsForGroup(String group) { + List<PinnedFile> filesInGroup; + synchronized (this) { + filesInGroup = mPinnedFiles.values() + .stream() + .filter(pf -> pf.groupName.equals(group)) + .toList(); + } + return filesInGroup; + } + public void unpinGroup(String group) { + List<PinnedFile> pinnedFiles = getAllPinsForGroup(group); + for (PinnedFile pf : pinnedFiles) { + unpinFile(pf.fileName); + } + } + + public void unpinFile(String filename) { + PinnedFile pinnedFile; + synchronized (this) { + pinnedFile = mPinnedFiles.get(filename); + } + if (pinnedFile == null) { + // File not pinned, nothing to do. + return; + } + pinnedFile.close(); + synchronized (this) { + if (DEBUG) { + Slog.d(TAG, "Unpinned file: " + filename); + } + mPinnedFiles.remove(pinnedFile.fileName); + for (PinnedFile dep : pinnedFile.pinnedDeps) { + if (dep == null) { + continue; + } + mPinnedFiles.remove(dep.fileName); + if (DEBUG) { + Slog.d(TAG, "Unpinned dependency: " + dep.fileName); + } + } + } + } private static int clamp(int min, int value, int max) { return Math.max(min, Math.min(value, max)); @@ -1205,17 +1354,44 @@ public final class PinnerService extends SystemService { } } - private final class BinderService extends Binder { + public List<PinnedFileStat> getPinnerStats() { + ArrayList<PinnedFileStat> stats = new ArrayList<>(); + Collection<PinnedApp> pinnedApps; + synchronized(this) { + pinnedApps = mPinnedApps.values(); + } + for (PinnedApp pinnedApp : pinnedApps) { + for (PinnedFile pf : pinnedApp.mFiles) { + PinnedFileStat stat = + new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName); + stats.add(stat); + } + } + + Collection<PinnedFile> pinnedFiles; + synchronized(this) { + pinnedFiles = mPinnedFiles.values(); + } + for (PinnedFile pf : pinnedFiles) { + PinnedFileStat stat = new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName); + stats.add(stat); + } + if (mCurrentlyPinnedAnonSize > 0) { + stats.add(new PinnedFileStat(ANON_REGION_STAT_NAME, + mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME)); + } + return stats; + } + + public final class BinderService extends IPinnerService.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + HashSet<PinnedFile> shownPins = new HashSet<>(); + HashSet<String> groups = new HashSet<>(); + final int bytesPerMB = 1024 * 1024; synchronized (PinnerService.this) { long totalSize = 0; - for (PinnedFile pinnedFile : mPinnedFiles) { - pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned); - totalSize += pinnedFile.bytesPinned; - } - pw.println(); for (int key : mPinnedApps.keySet()) { PinnedApp app = mPinnedApps.get(key); pw.print(getNameForKey(key)); @@ -1223,14 +1399,53 @@ public final class PinnerService extends SystemService { pw.print(" active="); pw.print(app.active); pw.println(); for (PinnedFile pf : mPinnedApps.get(key).mFiles) { - pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned); + pw.print(" "); + pw.format("%s pinned:%d bytes (%d MB) pinlist:%b\n", pf.fileName, + pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist); totalSize += pf.bytesPinned; + shownPins.add(pf); + for (PinnedFile dep : pf.pinnedDeps) { + pw.print(" "); + pw.format("%s pinned:%d bytes (%d MB) pinlist:%b (Dependency)\n", dep.fileName, + dep.bytesPinned, dep.bytesPinned / bytesPerMB, dep.used_pinlist); + totalSize += dep.bytesPinned; + shownPins.add(dep); + } + } + } + pw.println(); + for (PinnedFile pinnedFile : mPinnedFiles.values()) { + if (!groups.contains(pinnedFile.groupName)) { + groups.add(pinnedFile.groupName); } } + boolean firstPinInGroup = true; + for (String group : groups) { + List<PinnedFile> groupPins = getAllPinsForGroup(group); + for (PinnedFile pinnedFile : groupPins) { + if (shownPins.contains(pinnedFile)) { + // Already showed in the dump and accounted for, skip. + continue; + } + if (firstPinInGroup) { + firstPinInGroup = false; + // Ensure we only print when there are pins for groups not yet shown + // in the pinned app section. + pw.print("Group:" + group); + pw.println(); + } + pw.format(" %s pinned:%d bytes (%d MB) pinlist:%b\n", pinnedFile.fileName, + pinnedFile.bytesPinned, pinnedFile.bytesPinned / bytesPerMB, + pinnedFile.used_pinlist); + totalSize += pinnedFile.bytesPinned; + } + } + pw.println(); if (mPinAnonAddress != 0) { - pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize); + pw.format("Pinned anon region: %d (%d MB)\n", mCurrentlyPinnedAnonSize, mCurrentlyPinnedAnonSize / bytesPerMB); + totalSize += mCurrentlyPinnedAnonSize; } - pw.format("Total size: %s\n", totalSize); + pw.format("Total pinned: %s bytes (%s MB)\n", totalSize, totalSize / bytesPerMB); pw.println(); if (!mPendingRepin.isEmpty()) { pw.print("Pending repin: "); @@ -1277,14 +1492,29 @@ public final class PinnerService extends SystemService { resultReceiver.send(0, null); } + + @EnforcePermission(android.Manifest.permission.DUMP) + @Override + public List<PinnedFileStat> getPinnerStats() { + getPinnerStats_enforcePermission(); + return PinnerService.this.getPinnerStats(); + } } - private static final class PinnedFile implements AutoCloseable { + @VisibleForTesting + public static final class PinnedFile implements AutoCloseable { private long mAddress; final int mapSize; final String fileName; final int bytesPinned; + // Whether this file was pinned using a pinlist + boolean used_pinlist; + + // User defined group name for pinner accounting + String groupName = ""; + ArrayList<PinnedFile> pinnedDeps = new ArrayList<>(); + PinnedFile(long address, int mapSize, String fileName, int bytesPinned) { mAddress = address; this.mapSize = mapSize; @@ -1298,6 +1528,11 @@ public final class PinnerService extends SystemService { safeMunmap(mAddress, mapSize); mAddress = -1; } + for (PinnedFile dep : pinnedDeps) { + if (dep != null) { + dep.close(); + } + } } @Override @@ -1355,5 +1590,4 @@ public final class PinnerService extends SystemService { } } } - } diff --git a/services/core/java/com/android/server/SecurityStateManagerService.java b/services/core/java/com/android/server/SecurityStateManagerService.java new file mode 100644 index 000000000000..98039be20897 --- /dev/null +++ b/services/core/java/com/android/server/SecurityStateManagerService.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 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; + +import static android.os.SecurityStateManager.KEY_KERNEL_VERSION; +import static android.os.SecurityStateManager.KEY_SYSTEM_SPL; +import static android.os.SecurityStateManager.KEY_VENDOR_SPL; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.ISecurityStateManager; +import android.os.SystemProperties; +import android.os.VintfRuntimeInfo; +import android.text.TextUtils; +import android.util.Slog; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewUpdateService; + +import com.android.internal.R; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SecurityStateManagerService extends ISecurityStateManager.Stub { + + private static final String TAG = "SecurityStateManagerService"; + + static final String VENDOR_SECURITY_PATCH_PROPERTY_KEY = "ro.vendor.build" + + ".security_patch"; + static final Pattern KERNEL_RELEASE_PATTERN = Pattern.compile("(\\d+\\.\\d+\\.\\d+)(" + + ".*)"); + + private final Context mContext; + private final PackageManager mPackageManager; + + public SecurityStateManagerService(Context context) { + mContext = context; + mPackageManager = context.getPackageManager(); + } + + @Override + public Bundle getGlobalSecurityState() { + Bundle globalSecurityState = new Bundle(); + globalSecurityState.putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH); + globalSecurityState.putString(KEY_VENDOR_SPL, + SystemProperties.get(VENDOR_SECURITY_PATCH_PROPERTY_KEY, "")); + String moduleMetadataProviderPackageName = + mContext.getString(R.string.config_defaultModuleMetadataProvider); + if (!moduleMetadataProviderPackageName.isEmpty()) { + globalSecurityState.putString(moduleMetadataProviderPackageName, + getSpl(moduleMetadataProviderPackageName)); + } + globalSecurityState.putString(KEY_KERNEL_VERSION, getKernelVersion()); + addWebViewPackages(globalSecurityState); + addSecurityStatePackages(globalSecurityState); + return globalSecurityState; + } + + private String getSpl(String packageName) { + if (!TextUtils.isEmpty(packageName)) { + try { + return mPackageManager.getPackageInfo(packageName, 0 /* flags */).versionName; + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, TextUtils.formatSimple("Failed to get SPL for package %s.", + packageName), e); + } + } + return ""; + } + + private String getKernelVersion() { + Matcher matcher = KERNEL_RELEASE_PATTERN.matcher(VintfRuntimeInfo.getKernelVersion()); + if (matcher.matches()) { + return matcher.group(1); + } + return ""; + } + + private void addWebViewPackages(Bundle bundle) { + for (WebViewProviderInfo info : WebViewUpdateService.getAllWebViewPackages()) { + String packageName = info.packageName; + bundle.putString(packageName, getSpl(packageName)); + } + } + + private void addSecurityStatePackages(Bundle bundle) { + String[] packageNames; + packageNames = mContext.getResources().getStringArray(R.array.config_securityStatePackages); + for (String packageName : packageNames) { + bundle.putString(packageName, getSpl(packageName)); + } + } +} diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index f6835feeea16..39b8643e6d38 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -215,7 +215,7 @@ class StorageManagerService extends IStorageManager.Stub public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10; /** Extended timeout for the system server watchdog. */ - private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000; + private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 20 * 1000; /** Extended timeout for the system server watchdog for vold#partition operation. */ private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000; @@ -1235,11 +1235,16 @@ class StorageManagerService extends IStorageManager.Stub } } + private void extendWatchdogTimeout(String reason) { + Watchdog w = Watchdog.getInstance(); + w.pauseWatchingMonitorsFor(SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, reason); + w.pauseWatchingCurrentThreadFor(SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, reason); + } + private void onUserStopped(int userId) { Slog.d(TAG, "onUserStopped " + userId); - Watchdog.getInstance().pauseWatchingMonitorsFor( - SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow"); + extendWatchdogTimeout("#onUserStopped might be slow"); try { mVold.onUserStopped(userId); mStoraged.onUserStopped(userId); @@ -1322,8 +1327,7 @@ class StorageManagerService extends IStorageManager.Stub unlockedUsers.add(userId); } } - Watchdog.getInstance().pauseWatchingMonitorsFor( - SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow"); + extendWatchdogTimeout("#onUserStopped might be slow"); for (Integer userId : unlockedUsers) { try { mVold.onUserStopped(userId); @@ -2343,8 +2347,7 @@ class StorageManagerService extends IStorageManager.Stub try { // TODO(b/135341433): Remove cautious logging when FUSE is stable Slog.i(TAG, "Mounting volume " + vol); - Watchdog.getInstance().pauseWatchingMonitorsFor( - SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow"); + extendWatchdogTimeout("#mount might be slow"); mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() { @Override public boolean onVolumeChecking(FileDescriptor fd, String path, @@ -2474,8 +2477,7 @@ class StorageManagerService extends IStorageManager.Stub final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); - Watchdog.getInstance().pauseWatchingMonitorsFor( - PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); + extendWatchdogTimeout("#partition might be slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1); waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2493,8 +2495,7 @@ class StorageManagerService extends IStorageManager.Stub final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); - Watchdog.getInstance().pauseWatchingMonitorsFor( - PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); + extendWatchdogTimeout("#partition might be slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1); waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2512,8 +2513,7 @@ class StorageManagerService extends IStorageManager.Stub final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); - Watchdog.getInstance().pauseWatchingMonitorsFor( - PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); + extendWatchdogTimeout("#partition might be slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio); waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -3622,8 +3622,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public ParcelFileDescriptor open() throws AppFuseMountException { - Watchdog.getInstance().pauseWatchingMonitorsFor( - SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow"); + extendWatchdogTimeout("#open might be slow"); try { final FileDescriptor fd = mVold.mountAppFuse(uid, mountId); mMounted = true; @@ -3636,8 +3635,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public ParcelFileDescriptor openFile(int mountId, int fileId, int flags) throws AppFuseMountException { - Watchdog.getInstance().pauseWatchingMonitorsFor( - SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow"); + extendWatchdogTimeout("#openFile might be slow"); try { return new ParcelFileDescriptor( mVold.openAppFuseFile(uid, mountId, fileId, flags)); @@ -3648,8 +3646,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public void close() throws Exception { - Watchdog.getInstance().pauseWatchingMonitorsFor( - SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow"); + extendWatchdogTimeout("#close might be slow"); if (mMounted) { BackgroundThread.getHandler().post(() -> { try { diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index c718d392a610..9eb35fde50fb 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2096,14 +2096,48 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { /** * Send a notification to registrants about the data activity state. * + * @param subId the subscriptionId for the data connection + * @param state indicates the latest data activity type + * e.g.,{@link TelephonyManager#DATA_ACTIVITY_IN} + * + */ + + public void notifyDataActivityForSubscriber(int subId, int state) { + if (!checkNotifyPermission("notifyDataActivity()")) { + return; + } + int phoneId = getPhoneIdFromSubId(subId); + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + mDataActivity[phoneId] = state; + for (Record r : mRecords) { + // Notify by correct subId. + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + r.callback.onDataActivity(state); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + + /** + * Send a notification to registrants about the data activity state. + * * @param phoneId the phoneId carrying the data connection * @param subId the subscriptionId for the data connection * @param state indicates the latest data activity type * e.g.,{@link TelephonyManager#DATA_ACTIVITY_IN} * */ - public void notifyDataActivityForSubscriber(int phoneId, int subId, int state) { - if (!checkNotifyPermission("notifyDataActivity()" )) { + public void notifyDataActivityForSubscriberWithSlot(int phoneId, int subId, int state) { + if (!checkNotifyPermission("notifyDataActivityWithSlot()")) { return; } diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 003046ab884f..60f087f53a7e 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -69,6 +69,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -98,11 +99,17 @@ public class Watchdog implements Dumpable { // applications may not work with a debug build. CTS will fail. private static final long DEFAULT_TIMEOUT = DB ? 10 * 1000 : 60 * 1000; + // This ratio is used to compute the pre-watchdog timeout (2 means that the pre-watchdog timeout + // will be half the full timeout). + // + // The pre-watchdog event is similar to a full watchdog except it does not crash system server. + private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 2; + // These are temporally ordered: larger values as lateness increases - private static final int COMPLETED = 0; - private static final int WAITING = 1; - private static final int WAITED_HALF = 2; - private static final int OVERDUE = 3; + static final int COMPLETED = 0; + static final int WAITING = 1; + static final int WAITED_UNTIL_PRE_WATCHDOG = 2; + static final int OVERDUE = 3; // Track watchdog timeout history and break the crash loop if there is. private static final String TIMEOUT_HISTORY_FILE = "/data/system/watchdog-timeout-history.txt"; @@ -237,10 +244,8 @@ public class Watchdog implements Dumpable { } } - /** - * Used for checking status of handle threads and scheduling monitor callbacks. - */ - public final class HandlerChecker implements Runnable { + /** Used for checking status of handle threads and scheduling monitor callbacks. */ + public static class HandlerChecker implements Runnable { private final Handler mHandler; private final String mName; private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); @@ -251,11 +256,19 @@ public class Watchdog implements Dumpable { private long mStartTimeMillis; private int mPauseCount; private long mPauseEndTimeMillis; + private Clock mClock; + private Object mLock; - HandlerChecker(Handler handler, String name) { + HandlerChecker(Handler handler, String name, Object lock, Clock clock) { mHandler = handler; mName = name; + mLock = lock; mCompleted = true; + mClock = clock; + } + + HandlerChecker(Handler handler, String name, Object lock) { + this(handler, name, lock, SystemClock.uptimeClock()); } void addMonitorLocked(Monitor monitor) { @@ -278,11 +291,9 @@ public class Watchdog implements Dumpable { mMonitorQueue.clear(); } - long nowMillis = SystemClock.uptimeMillis(); - boolean isPaused = mPauseCount > 0 - || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis); - if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) - || isPaused) { + long nowMillis = mClock.millis(); + boolean isPaused = mPauseCount > 0 || mPauseEndTimeMillis > nowMillis; + if ((mMonitors.size() == 0 && isHandlerPolling()) || isPaused) { // Don't schedule until after resume OR // If the target looper has recently been polling, then // there is no reason to enqueue our checker on it since that @@ -305,15 +316,19 @@ public class Watchdog implements Dumpable { mHandler.postAtFrontOfQueue(this); } + boolean isHandlerPolling() { + return mHandler.getLooper().getQueue().isPolling(); + } + public int getCompletionStateLocked() { if (mCompleted) { return COMPLETED; } else { - long latency = SystemClock.uptimeMillis() - mStartTimeMillis; - if (latency < mWaitMaxMillis / 2) { + long latency = mClock.millis() - mStartTimeMillis; + if (latency < mWaitMaxMillis / PRE_WATCHDOG_TIMEOUT_RATIO) { return WAITING; } else if (latency < mWaitMaxMillis) { - return WAITED_HALF; + return WAITED_UNTIL_PRE_WATCHDOG; } } return OVERDUE; @@ -334,7 +349,7 @@ public class Watchdog implements Dumpable { } else { prefix = "Blocked in monitor " + mCurrentMonitor.getClass().getName(); } - long latencySeconds = (SystemClock.uptimeMillis() - mStartTimeMillis) / 1000; + long latencySeconds = (mClock.millis() - mStartTimeMillis) / 1000; return prefix + " on " + mName + " (" + getThread().getName() + ")" + " for " + latencySeconds + "s"; } @@ -366,10 +381,11 @@ public class Watchdog implements Dumpable { * the given time. */ public void pauseForLocked(int pauseMillis, String reason) { - mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis; + mPauseEndTimeMillis = mClock.millis() + pauseMillis; // Mark as completed, because there's a chance we called this after the watchog - // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure - // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED' + // thread loop called Object#wait after 'WAITED_UNTIL_PRE_WATCHDOG'. In that case we + // want to ensure the next call to #getCompletionStateLocked for this checker returns + // 'COMPLETED' mCompleted = true; Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: " + reason + ". Pause end time: " + mPauseEndTimeMillis); @@ -379,8 +395,9 @@ public class Watchdog implements Dumpable { public void pauseLocked(String reason) { mPauseCount++; // Mark as completed, because there's a chance we called this after the watchog - // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure - // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED' + // thread loop called Object#wait after 'WAITED_UNTIL_PRE_WATCHDOG'. In that case we + // want to ensure the next call to #getCompletionStateLocked for this checker returns + // 'COMPLETED' mCompleted = true; Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: " + reason + ". Pause count: " + mPauseCount); @@ -396,6 +413,11 @@ public class Watchdog implements Dumpable { Slog.wtf(TAG, "Already resumed HandlerChecker: " + mName); } } + + @Override + public String toString() { + return "CheckerHandler for " + mName; + } } final class RebootRequestReceiver extends BroadcastReceiver { @@ -445,31 +467,40 @@ public class Watchdog implements Dumpable { ServiceThread t = new ServiceThread("watchdog.monitor", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/); t.start(); - mMonitorChecker = new HandlerChecker(new Handler(t.getLooper()), "monitor thread"); + mMonitorChecker = new HandlerChecker(new Handler(t.getLooper()), "monitor thread", mLock); mHandlerCheckers.add(withDefaultTimeout(mMonitorChecker)); - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(FgThread.getHandler(), "foreground thread"))); + mHandlerCheckers.add( + withDefaultTimeout( + new HandlerChecker(FgThread.getHandler(), "foreground thread", mLock))); // Add checker for main thread. We only do a quick check since there // can be UI running on the thread. - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(new Handler(Looper.getMainLooper()), "main thread"))); + mHandlerCheckers.add( + withDefaultTimeout( + new HandlerChecker( + new Handler(Looper.getMainLooper()), "main thread", mLock))); // Add checker for shared UI thread. - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(UiThread.getHandler(), "ui thread"))); + mHandlerCheckers.add( + withDefaultTimeout(new HandlerChecker(UiThread.getHandler(), "ui thread", mLock))); // And also check IO thread. - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(IoThread.getHandler(), "i/o thread"))); + mHandlerCheckers.add( + withDefaultTimeout(new HandlerChecker(IoThread.getHandler(), "i/o thread", mLock))); // And the display thread. - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(DisplayThread.getHandler(), "display thread"))); + mHandlerCheckers.add( + withDefaultTimeout( + new HandlerChecker(DisplayThread.getHandler(), "display thread", mLock))); // And the animation thread. - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(AnimationThread.getHandler(), "animation thread"))); + mHandlerCheckers.add( + withDefaultTimeout( + new HandlerChecker( + AnimationThread.getHandler(), "animation thread", mLock))); // And the surface animation thread. - mHandlerCheckers.add(withDefaultTimeout( - new HandlerChecker(SurfaceAnimationThread.getHandler(), - "surface animation thread"))); + mHandlerCheckers.add( + withDefaultTimeout( + new HandlerChecker( + SurfaceAnimationThread.getHandler(), + "surface animation thread", + mLock))); // Initialize monitor for Binder threads. addMonitor(new BinderThreadMonitor()); @@ -609,7 +640,7 @@ public class Watchdog implements Dumpable { public void addThread(Handler thread) { synchronized (mLock) { final String name = thread.getLooper().getThread().getName(); - mHandlerCheckers.add(withDefaultTimeout(new HandlerChecker(thread, name))); + mHandlerCheckers.add(withDefaultTimeout(new HandlerChecker(thread, name, mLock))); } } @@ -617,7 +648,7 @@ public class Watchdog implements Dumpable { synchronized (mLock) { final String name = thread.getLooper().getThread().getName(); mHandlerCheckers.add( - withCustomTimeout(new HandlerChecker(thread, name), timeoutMillis)); + withCustomTimeout(new HandlerChecker(thread, name, mLock), timeoutMillis)); } } @@ -797,11 +828,11 @@ public class Watchdog implements Dumpable { String subject = ""; boolean allowRestart = true; int debuggerWasConnected = 0; - boolean doWaitedHalfDump = false; + boolean doWaitedPreDump = false; // The value of mWatchdogTimeoutMillis might change while we are executing the loop. // We store the current value to use a consistent value for all handlers. final long watchdogTimeoutMillis = mWatchdogTimeoutMillis; - final long checkIntervalMillis = watchdogTimeoutMillis / 2; + final long checkIntervalMillis = watchdogTimeoutMillis / PRE_WATCHDOG_TIMEOUT_RATIO; final ArrayList<Integer> pids; synchronized (mLock) { long timeout = checkIntervalMillis; @@ -848,15 +879,16 @@ public class Watchdog implements Dumpable { } else if (waitState == WAITING) { // still waiting but within their configured intervals; back off and recheck continue; - } else if (waitState == WAITED_HALF) { + } else if (waitState == WAITED_UNTIL_PRE_WATCHDOG) { if (!waitedHalf) { - Slog.i(TAG, "WAITED_HALF"); + Slog.i(TAG, "WAITED_UNTIL_PRE_WATCHDOG"); waitedHalf = true; - // We've waited half, but we'd need to do the stack trace dump w/o the lock. - blockedCheckers = getCheckersWithStateLocked(WAITED_HALF); + // We've waited until the pre-watchdog, but we'd need to do the stack trace + // dump w/o the lock. + blockedCheckers = getCheckersWithStateLocked(WAITED_UNTIL_PRE_WATCHDOG); subject = describeCheckersLocked(blockedCheckers); pids = new ArrayList<>(mInterestingJavaPids); - doWaitedHalfDump = true; + doWaitedPreDump = true; } else { continue; } @@ -874,12 +906,12 @@ public class Watchdog implements Dumpable { // First collect stack traces from all threads of the system process. // // Then, if we reached the full timeout, kill this process so that the system will - // restart. If we reached half of the timeout, just log some information and continue. - logWatchog(doWaitedHalfDump, subject, pids); + // restart. If we reached pre-watchdog timeout, just log some information and continue. + logWatchog(doWaitedPreDump, subject, pids); - if (doWaitedHalfDump) { - // We have waited for only half of the timeout, we continue to wait for the duration - // of the full timeout before killing the process. + if (doWaitedPreDump) { + // We have waited for only pre-watchdog timeout, we continue to wait for the + // duration of the full timeout before killing the process. continue; } @@ -928,8 +960,8 @@ public class Watchdog implements Dumpable { } } - private void logWatchog(boolean halfWatchdog, String subject, ArrayList<Integer> pids) { - // Get critical event log before logging the half watchdog so that it doesn't + private void logWatchog(boolean preWatchdog, String subject, ArrayList<Integer> pids) { + // Get critical event log before logging the pre-watchdog so that it doesn't // occur in the log. String criticalEvents = CriticalEventLog.getInstance().logLinesForSystemServerTraceFile(); @@ -941,7 +973,7 @@ public class Watchdog implements Dumpable { } final String dropboxTag; - if (halfWatchdog) { + if (preWatchdog) { dropboxTag = "pre_watchdog"; CriticalEventLog.getInstance().logHalfWatchdog(subject); FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED); @@ -971,7 +1003,7 @@ public class Watchdog implements Dumpable { report.append(processCpuTracker.printCurrentState(anrTime, 10)); report.append(tracesFileException.getBuffer()); - if (!halfWatchdog) { + if (!preWatchdog) { // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the // kernel log doSysRq('w'); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 54c8ed38bb1c..b87d02d86c22 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -79,7 +79,6 @@ import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_ import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; import static android.os.PowerExemptionManager.REASON_BOOT_COMPLETED; import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER; -import static android.os.PowerExemptionManager.REASON_DENIED; import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED; import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP; @@ -5051,7 +5050,7 @@ public class ActivityManagerService extends IActivityManager.Stub REASON_LOCKED_BOOT_COMPLETED); } // Send BOOT_COMPLETED if the user is unlocked - if (StorageManager.isUserKeyUnlocked(app.userId)) { + if (StorageManager.isCeStorageUnlocked(app.userId)) { sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED), REASON_BOOT_COMPLETED); } @@ -14150,7 +14149,8 @@ public class ActivityManagerService extends IActivityManager.Stub || action.startsWith("android.intent.action.PACKAGE_") || action.startsWith("android.intent.action.UID_") || action.startsWith("android.intent.action.EXTERNAL_") - || action.startsWith("android.bluetooth.")) { + || action.startsWith("android.bluetooth.") + || action.equals(Intent.ACTION_SHUTDOWN)) { if (DEBUG_BROADCAST) { Slog.wtf(TAG, "System internals registering for " + filter.toLongString() diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index e65603021708..1928780fa090 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -3013,7 +3013,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies, null); + mCpuScalingPolicies, new PowerStatsUidResolver()); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpProtoLocked(mContext, fd, apps, flags, @@ -3055,7 +3055,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies, null); + mCpuScalingPolicies, new PowerStatsUidResolver()); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpCheckin(mContext, pw, apps, flags, diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 192fd6f1a9ce..f47482d3a70f 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -142,6 +142,7 @@ public class SettingsToPropertiesMapper { "core_experiments_team_internal", "core_graphics", "dck_framework", + "devoptions_settings", "game", "haptics", "hardware_backed_security_mainline", @@ -152,11 +153,13 @@ public class SettingsToPropertiesMapper { "mainline_sdk", "media_audio", "media_drm", + "media_reliability", "media_tv", "media_solutions", "nfc", "pdf_viewer", "pixel_audio_android", + "pixel_bluetooth", "pixel_system_sw_touch", "pixel_watch", "platform_security", diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d4b72c1770f7..5d47e35a8729 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4600,7 +4600,7 @@ public class AudioService extends IAudioService.Stub } private void setStreamVolume(int streamType, int index, int flags, - @NonNull AudioDeviceAttributes ada, + @Nullable AudioDeviceAttributes ada, String callingPackage, String caller, String attributionTag, int uid, boolean hasModifyAudioSettings, boolean canChangeMuteAndUpdateController) { @@ -4618,7 +4618,9 @@ public class AudioService extends IAudioService.Stub int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; - final int device = ada.getInternalType(); + final int device = (ada == null) + ? getDeviceForStream(streamType) + : ada.getInternalType(); int oldIndex; // skip a2dp absolute volume control request when the device @@ -10665,8 +10667,8 @@ public class AudioService extends IAudioService.Stub /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */ @Override - public void addLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) { - mLoudnessCodecHelper.addLoudnessCodecInfo(piid, codecInfo); + public void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo codecInfo) { + mLoudnessCodecHelper.addLoudnessCodecInfo(piid, mediaCodecHash, codecInfo); } /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */ @@ -11370,6 +11372,8 @@ public class AudioService extends IAudioService.Stub static final int LOG_NB_EVENTS_SPATIAL = 30; static final int LOG_NB_EVENTS_SOUND_DOSE = 30; + static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30; + static final EventLogger sLifecycleLogger = new EventLogger(LOG_NB_EVENTS_LIFECYCLE, "audio services lifecycle"); @@ -11569,6 +11573,10 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.dump(pw); sSpatialLogger.dump(pw); + pw.println("\n"); + pw.println("\nLoudness alignment:"); + mLoudnessCodecHelper.dump(pw); + mAudioSystem.dump(pw); } diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index f69b9f6523cc..de8901179028 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -622,6 +622,55 @@ public class AudioServiceEvents { } } + static final class LoudnessEvent extends EventLogger.Event { + static final int START_PIID = 0; + + static final int STOP_PIID = 1; + + static final int CLIENT_DIED = 2; + + final int mEventType; + final int mIntValue1; + final int mIntValue2; + + private LoudnessEvent(int event, int i1, int i2) { + mEventType = event; + mIntValue1 = i1; + mIntValue2 = i2; + } + + static LoudnessEvent getStartPiid(int piid, int pid) { + return new LoudnessEvent(START_PIID, piid, pid); + } + + static LoudnessEvent getStopPiid(int piid, int pid) { + return new LoudnessEvent(STOP_PIID, piid, pid); + } + + static LoudnessEvent getClientDied(int pid) { + return new LoudnessEvent(CLIENT_DIED, 0 /* ignored */, pid); + } + + + @Override + public String eventToString() { + switch (mEventType) { + case START_PIID: + return String.format( + "Start loudness updates for piid %d for client pid %d", + mIntValue1, mIntValue2); + case STOP_PIID: + return String.format( + "Stop loudness updates for piid %d for client pid %d", + mIntValue1, mIntValue2); + case CLIENT_DIED: + return String.format("Loudness client with pid %d died", mIntValue2); + + } + return new StringBuilder("FIXME invalid event type:").append(mEventType).toString(); + } + } + /** * Class to log stream type mute/unmute events */ diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java index 3c67e9dd116b..bbe819f22e3a 100644 --- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java +++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java @@ -21,6 +21,11 @@ import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH; import static android.media.AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID; +import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; +import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; +import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; +import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; +import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; import android.annotation.IntDef; import android.annotation.NonNull; @@ -41,7 +46,11 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.audio.AudioServiceEvents.LoudnessEvent; +import com.android.server.utils.EventLogger; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -50,6 +59,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * Class to handle the updates in loudness parameters and responsible to generate parameters that @@ -70,10 +80,14 @@ public class LoudnessCodecHelper { private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE = "audio.loudness.builtin-speaker-spl-range-size"; - private static final int SPL_RANGE_UNKNOWN = 0; - private static final int SPL_RANGE_SMALL = 1; - private static final int SPL_RANGE_MEDIUM = 2; - private static final int SPL_RANGE_LARGE = 3; + @VisibleForTesting + static final int SPL_RANGE_UNKNOWN = 0; + @VisibleForTesting + static final int SPL_RANGE_SMALL = 1; + @VisibleForTesting + static final int SPL_RANGE_MEDIUM = 2; + @VisibleForTesting + static final int SPL_RANGE_LARGE = 3; /** The possible transducer SPL ranges as defined in CTA2075 */ @IntDef({ @@ -99,12 +113,19 @@ public class LoudnessCodecHelper { pid = (Integer) cookie; } if (pid != null) { - mLoudnessCodecHelper.removePid(pid); + if (DEBUG) { + Log.d(TAG, "Client with pid " + pid + " died, removing from receiving updates"); + } + sLogger.enqueue(LoudnessEvent.getClientDied(pid)); + mLoudnessCodecHelper.onClientPidDied(pid); } super.onCallbackDied(callback, cookie); } } + private static final EventLogger sLogger = new EventLogger( + AudioService.LOG_NB_EVENTS_LOUDNESS_CODEC, "Loudness updates"); + private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers = new LoudnessRemoteCallbackList(this); @@ -125,7 +146,8 @@ public class LoudnessCodecHelper { private final AudioService mAudioService; /** Contains the properties necessary to compute the codec loudness related parameters. */ - private static final class LoudnessCodecInputProperties { + @VisibleForTesting + static final class LoudnessCodecInputProperties { private final int mMetadataType; private final boolean mIsDownmixing; @@ -200,10 +222,53 @@ public class LoudnessCodecHelper { } PersistableBundle createLoudnessParameters() { - // TODO: create bundle with new parameters - return new PersistableBundle(); - } + PersistableBundle loudnessParams = new PersistableBundle(); + + switch (mDeviceSplRange) { + case SPL_RANGE_LARGE: + // corresponds to -31dB attenuation + loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 124); + if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { + loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 0); + } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { + // general compression + loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6); + } + break; + case SPL_RANGE_MEDIUM: + // corresponds to -24dB attenuation + loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96); + if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { + loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0); + } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { + // general compression + loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6); + } + break; + case SPL_RANGE_SMALL: + // corresponds to -16dB attenuation + loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 64); + if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { + loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 1); + } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { + // limited playback range compression + loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 3); + } + break; + default: + // corresponds to -24dB attenuation + loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96); + if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { + loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0); + } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { + // general compression + loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6); + } + break; + } + return loudnessParams; + } } @GuardedBy("mLock") @@ -227,22 +292,25 @@ public class LoudnessCodecHelper { if (DEBUG) { Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList); } - Set<LoudnessCodecInfo> infoSet; + synchronized (mLock) { if (mStartedPiids.contains(piid)) { Log.w(TAG, "Already started loudness updates for piid " + piid); return; } - infoSet = new HashSet<>(codecInfoList); + Set<LoudnessCodecInfo> infoSet = new HashSet<>(codecInfoList); mStartedPiids.put(piid, infoSet); - mPiidToPidCache.put(piid, Binder.getCallingPid()); + int pid = Binder.getCallingPid(); + mPiidToPidCache.put(piid, pid); + + sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid)); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mAudioService.getActivePlaybackConfigurations().stream().filter( conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent( - apc -> updateCodecParametersForConfiguration(apc, infoSet)); + this::updateCodecParametersForConfiguration); } } @@ -250,20 +318,24 @@ public class LoudnessCodecHelper { if (DEBUG) { Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid); } + synchronized (mLock) { if (!mStartedPiids.contains(piid)) { Log.w(TAG, "Loudness updates are already stopped for piid " + piid); return; } mStartedPiids.remove(piid); + + sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1))); mPiidToDeviceIdCache.delete(piid); mPiidToPidCache.delete(piid); } } - void addLoudnessCodecInfo(int piid, LoudnessCodecInfo info) { + void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo info) { if (DEBUG) { - Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " info " + info); + Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " mcHash " + mediaCodecHash + " info " + + info); } Set<LoudnessCodecInfo> infoSet; @@ -280,7 +352,20 @@ public class LoudnessCodecHelper { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mAudioService.getActivePlaybackConfigurations().stream().filter( conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent( - apc -> updateCodecParametersForConfiguration(apc, Set.of(info))); + apc -> { + final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); + if (deviceInfo != null) { + PersistableBundle updateBundle = new PersistableBundle(); + synchronized (mLock) { + updateBundle.putPersistableBundle( + Integer.toString(mediaCodecHash), + getCodecBundle_l(deviceInfo, info)); + } + if (!updateBundle.isDefinitelyEmpty()) { + dispatchNewLoudnessParameters(piid, updateBundle); + } + } + }); } } @@ -298,24 +383,6 @@ public class LoudnessCodecHelper { } } - void removePid(int pid) { - if (DEBUG) { - Log.d(TAG, "Removing pid " + pid + " from receiving updates"); - } - synchronized (mLock) { - for (int i = 0; i < mPiidToPidCache.size(); ++i) { - int piid = mPiidToPidCache.keyAt(i); - if (mPiidToPidCache.get(piid) == pid) { - if (DEBUG) { - Log.d(TAG, "Removing piid " + piid); - } - mStartedPiids.delete(piid); - mPiidToDeviceIdCache.delete(piid); - } - } - } - } - PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) { if (DEBUG) { Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo); @@ -381,48 +448,77 @@ public class LoudnessCodecHelper { } } - updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc, null)); + updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc)); + } + + /** Updates and dispatches the new loudness parameters for all its registered codecs. */ + void dump(PrintWriter pw) { + // Registered clients + pw.println("\nRegistered clients:\n"); + synchronized (mLock) { + for (int i = 0; i < mStartedPiids.size(); ++i) { + int piid = mStartedPiids.keyAt(i); + int pid = mPiidToPidCache.get(piid, -1); + final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid); + pw.println(String.format("Player piid %d pid %d active codec types %s\n", piid, + pid, codecInfos.stream().map(Object::toString).collect( + Collectors.joining(", ")))); + } + pw.println(); + } + + sLogger.dump(pw); + pw.println(); } - /** Updates and dispatches the new loudness parameters for the {@code codecInfos} set. + private void onClientPidDied(int pid) { + synchronized (mLock) { + for (int i = 0; i < mPiidToPidCache.size(); ++i) { + int piid = mPiidToPidCache.keyAt(i); + if (mPiidToPidCache.get(piid) == pid) { + if (DEBUG) { + Log.d(TAG, "Removing piid " + piid); + } + mStartedPiids.delete(piid); + mPiidToDeviceIdCache.delete(piid); + } + } + } + } + + /** + * Updates and dispatches the new loudness parameters for the {@code codecInfos} set. * * @param apc the player configuration for which the loudness parameters are updated. - * @param codecInfos the codec info for which the parameters are updated. If {@code null}, - * send updates for all the started codecs assigned to {@code apc} */ - private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc, - Set<LoudnessCodecInfo> codecInfos) { + private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc) { if (DEBUG) { - Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc + " codecInfos: " - + codecInfos); + Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc); } + final PersistableBundle allBundles = new PersistableBundle(); final int piid = apc.getPlayerInterfaceId(); + synchronized (mLock) { - if (codecInfos == null) { - codecInfos = mStartedPiids.get(piid); - } + final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid); final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); if (codecInfos != null && deviceInfo != null) { for (LoudnessCodecInfo info : codecInfos) { - allBundles.putPersistableBundle(Integer.toString(info.mediaCodecHashCode), + allBundles.putPersistableBundle(Integer.toString(info.hashCode()), getCodecBundle_l(deviceInfo, info)); } } } if (!allBundles.isDefinitelyEmpty()) { - if (DEBUG) { - Log.d(TAG, "Dispatching for piid: " + piid + " bundle: " + allBundles); - } dispatchNewLoudnessParameters(piid, allBundles); } } private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) { if (DEBUG) { - Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid); + Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid + " bundle: " + bundle); } final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast(); for (int i = 0; i < nbDispatchers; ++i) { diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index be78ea20d09d..cecde55ef89f 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -149,6 +149,14 @@ public abstract class VirtualDeviceManagerInternal { public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId); /** + * Returns the ID of the device which owns the display with the given ID. + * + * <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then + * {@link android.content.Context#DEVICE_ID_DEFAULT} is returned.</p> + */ + public abstract int getDeviceIdForDisplayId(int displayId); + + /** * Gets the persistent ID for the VirtualDevice with the given device ID. * * @param deviceId which device we're asking about diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index debf828abf0a..d848f4b6cce5 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -121,6 +121,7 @@ public abstract class BrightnessMappingStrategy { // Display independent, mode dependent values float[] brightnessLevelsNits; + float[] brightnessLevels = null; float[] luxLevels; if (isForIdleMode) { brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( @@ -130,11 +131,21 @@ public abstract class BrightnessMappingStrategy { } else { brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(); luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(); + + brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(); + if (brightnessLevels == null || brightnessLevels.length == 0) { + // Load the old configuration in the range [0, 255]. The values need to be + // normalized to the range [0, 1]. + int[] brightnessLevelsInt = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + brightnessLevels = new float[brightnessLevelsInt.length]; + for (int i = 0; i < brightnessLevels.length; i++) { + brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]); + } + } } // Display independent, mode independent values - int[] brightnessLevelsBacklight = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); float autoBrightnessAdjustmentMaxGamma = resources.getFraction( com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1); @@ -155,8 +166,8 @@ public abstract class BrightnessMappingStrategy { builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange, autoBrightnessAdjustmentMaxGamma, isForIdleMode, displayWhiteBalanceController); - } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) { - return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight, + } else if (isValidMapping(luxLevels, brightnessLevels)) { + return new SimpleMappingStrategy(luxLevels, brightnessLevels, autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout); } else { return null; @@ -620,7 +631,7 @@ public abstract class BrightnessMappingStrategy { private float mUserBrightness; private long mShortTermModelTimeout; - private SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma, + private SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma, long timeout) { Preconditions.checkArgument(lux.length != 0 && brightness.length != 0, "Lux and brightness arrays must not be empty!"); @@ -635,7 +646,7 @@ public abstract class BrightnessMappingStrategy { mBrightness = new float[N]; for (int i = 0; i < N; i++) { mLux[i] = lux[i]; - mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]); + mBrightness[i] = brightness[i]; } mMaxGamma = maxGamma; diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java index 70d4ad2c6e1f..c26118eac7a2 100644 --- a/services/core/java/com/android/server/display/DisplayAdapter.java +++ b/services/core/java/com/android/server/display/DisplayAdapter.java @@ -20,6 +20,8 @@ import android.content.Context; import android.os.Handler; import android.view.Display; +import com.android.server.display.feature.DisplayManagerFlags; + import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; @@ -39,6 +41,7 @@ abstract class DisplayAdapter { private final Handler mHandler; private final Listener mListener; private final String mName; + private final DisplayManagerFlags mFeatureFlags; public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; @@ -50,13 +53,14 @@ abstract class DisplayAdapter { private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode. // Called with SyncRoot lock held. - public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, - Context context, Handler handler, Listener listener, String name) { + DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, + Listener listener, String name, DisplayManagerFlags featureFlags) { mSyncRoot = syncRoot; mContext = context; mHandler = handler; mListener = listener; mName = name; + mFeatureFlags = featureFlags; } /** @@ -88,6 +92,10 @@ abstract class DisplayAdapter { return mName; } + public final DisplayManagerFlags getFeatureFlags() { + return mFeatureFlags; + } + /** * Registers the display adapter with the display manager. * diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 2fdf90d7d286..3b05b47eb542 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -400,6 +400,7 @@ abstract class DisplayDevice { } private DisplayDeviceConfig loadDisplayDeviceConfig() { - return DisplayDeviceConfig.create(mContext, false); + return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false, + mDisplayAdapter.getFeatureFlags()); } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index b99de5cc0c7b..d97127c91fbf 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -57,6 +57,7 @@ import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; import com.android.server.display.config.IntegerArray; import com.android.server.display.config.LuxThrottling; +import com.android.server.display.config.LuxToBrightnessMapping; import com.android.server.display.config.NitsMap; import com.android.server.display.config.NonNegativeFloatToFloatPoint; import com.android.server.display.config.Point; @@ -77,6 +78,7 @@ import com.android.server.display.config.ThermalThrottling; import com.android.server.display.config.ThresholdPoint; import com.android.server.display.config.UsiVersion; import com.android.server.display.config.XmlParser; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.utils.DebugUtils; import org.xmlpull.v1.XmlPullParserException; @@ -310,16 +312,18 @@ import javax.xml.datatype.DatatypeConfigurationException; * <darkeningLightDebounceIdleMillis> * 1000 * </darkeningLightDebounceIdleMillis> - * <displayBrightnessMapping> - * <displayBrightnessPoint> - * <lux>50</lux> - * <nits>45.32</nits> - * </displayBrightnessPoint> - * <displayBrightnessPoint> - * <lux>80</lux> - * <nits>75.43</nits> - * </displayBrightnessPoint> - * </displayBrightnessMapping> + * <luxToBrightnessMapping> + * <map> + * <point> + * <first>0</first> + * <second>0.2</second> + * </point> + * <point> + * <first>80</first> + * <second>0.3</second> + * </point> + * </map> + * </luxToBrightnessMapping> * </autoBrightness> * * <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> @@ -528,6 +532,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <majorVersion>2</majorVersion> * <minorVersion>0</minorVersion> * </usiVersion> + * <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode> * </displayConfiguration> * } * </pre> @@ -629,7 +634,6 @@ public class DisplayDeviceConfig { // for the corresponding values above private float[] mBrightness; - /** * Array of desired screen brightness in nits corresponding to the lux values * in the mBrightnessLevelsLux array. The display brightness is defined as the @@ -639,20 +643,25 @@ public class DisplayDeviceConfig { private float[] mBrightnessLevelsNits; /** - * Array of light sensor lux values to define our levels for auto backlight - * brightness support. + * Array of desired screen brightness corresponding to the lux values + * in the mBrightnessLevelsLux array. The brightness values must be non-negative and + * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and + * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays + */ + private float[] mBrightnessLevels; + + /** + * Array of light sensor lux values to define our levels for auto-brightness support. * - * The N + 1 entries of this array define N control points defined in mBrightnessLevelsNits, - * with first value always being 0 lux + * The first lux value is always 0. * - * The control points must be strictly increasing. Each control point - * corresponds to an entry in the brightness backlight values arrays. - * For example, if lux == level[1] (second element of the levels array) - * then the brightness will be determined by value[0] (first element - * of the brightness values array). + * The control points must be strictly increasing. Each control point corresponds to an entry + * in the brightness values arrays. For example, if lux == luxLevels[1] (second element + * of the levels array) then the brightness will be determined by brightnessLevels[1] (second + * element of the brightness values array). * - * Spline interpolation is used to determine the auto-brightness - * backlight values for lux levels between these control points. + * Spline interpolation is used to determine the auto-brightness values for lux levels between + * these control points. */ private float[] mBrightnessLevelsLux; @@ -843,9 +852,17 @@ public class DisplayDeviceConfig { @Nullable private HdrBrightnessData mHdrBrightnessData; + /** + * Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode. + */ + private float mBrightnessCapForWearBedtimeMode; + + private final DisplayManagerFlags mFlags; + @VisibleForTesting - DisplayDeviceConfig(Context context) { + DisplayDeviceConfig(Context context, DisplayManagerFlags flags) { mContext = context; + mFlags = flags; } /** @@ -861,9 +878,9 @@ public class DisplayDeviceConfig { * @return A configuration instance for the specified display. */ public static DisplayDeviceConfig create(Context context, long physicalDisplayId, - boolean isFirstDisplay) { + boolean isFirstDisplay, DisplayManagerFlags flags) { final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId, - isFirstDisplay); + isFirstDisplay, flags); config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context)); return config; @@ -878,28 +895,29 @@ public class DisplayDeviceConfig { * or the default values. * @return A configuration instance. */ - public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { + public static DisplayDeviceConfig create(Context context, boolean useConfigXml, + DisplayManagerFlags flags) { final DisplayDeviceConfig config; if (useConfigXml) { - config = getConfigFromGlobalXml(context); + config = getConfigFromGlobalXml(context, flags); } else { - config = getConfigFromPmValues(context); + config = getConfigFromPmValues(context, flags); } return config; } private static DisplayDeviceConfig createWithoutDefaultValues(Context context, - long physicalDisplayId, boolean isFirstDisplay) { + long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) { DisplayDeviceConfig config; config = loadConfigFromDirectory(context, Environment.getProductDirectory(), - physicalDisplayId); + physicalDisplayId, flags); if (config != null) { return config; } config = loadConfigFromDirectory(context, Environment.getVendorDirectory(), - physicalDisplayId); + physicalDisplayId, flags); if (config != null) { return config; } @@ -907,7 +925,7 @@ public class DisplayDeviceConfig { // If no config can be loaded from any ddc xml at all, // prepare a whole config using the global config.xml. // Guaranteed not null - return create(context, isFirstDisplay); + return create(context, isFirstDisplay, flags); } private static DisplayConfiguration loadDefaultConfigurationXml(Context context) { @@ -960,18 +978,19 @@ public class DisplayDeviceConfig { } private static DisplayDeviceConfig loadConfigFromDirectory(Context context, - File baseDirectory, long physicalDisplayId) { + File baseDirectory, long physicalDisplayId, DisplayManagerFlags flags) { DisplayDeviceConfig config; // Create config using filename from physical ID (including "stable" bit). config = getConfigFromSuffix(context, baseDirectory, STABLE_ID_SUFFIX_FORMAT, - physicalDisplayId); + physicalDisplayId, flags); if (config != null) { return config; } // Create config using filename from physical ID (excluding "stable" bit). final long withoutStableFlag = physicalDisplayId & ~STABLE_FLAG; - config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag); + config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag, + flags); if (config != null) { return config; } @@ -980,7 +999,7 @@ public class DisplayDeviceConfig { final DisplayAddress.Physical physicalAddress = DisplayAddress.fromPhysicalDisplayId(physicalDisplayId); int port = physicalAddress.getPort(); - config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port); + config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port, flags); return config; } @@ -1599,6 +1618,13 @@ public class DisplayDeviceConfig { } /** + * @return Auto brightness brightening levels + */ + public float[] getAutoBrightnessBrighteningLevels() { + return mBrightnessLevels; + } + + /** * @return Default peak refresh rate of the associated display */ public int getDefaultPeakRefreshRate() { @@ -1741,6 +1767,13 @@ public class DisplayDeviceConfig { return mHostUsiVersion; } + /** + * @return Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode. + */ + public float getBrightnessCapForWearBedtimeMode() { + return mBrightnessCapForWearBedtimeMode; + } + @Override public String toString() { return "DisplayDeviceConfig{" @@ -1844,6 +1877,7 @@ public class DisplayDeviceConfig { + mAutoBrightnessDarkeningLightDebounceIdle + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux) + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits) + + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels) + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable + "\n" @@ -1867,36 +1901,39 @@ public class DisplayDeviceConfig { + ", mHighAmbientBrightnessThresholds= " + Arrays.toString(mHighAmbientBrightnessThresholds) + "\n" - + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString( + + "mScreenOffBrightnessSensorValueToLux= " + Arrays.toString( mScreenOffBrightnessSensorValueToLux) + "\n" + "mUsiVersion= " + mHostUsiVersion + "\n" - + "mHdrBrightnessData" + mHdrBrightnessData + + "mHdrBrightnessData= " + mHdrBrightnessData + "\n" + + "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode + "}"; } private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, - String suffixFormat, long idNumber) { + String suffixFormat, long idNumber, DisplayManagerFlags flags) { final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber); final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix); final File filePath = Environment.buildPath( baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename); - final DisplayDeviceConfig config = new DisplayDeviceConfig(context); + final DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags); if (config.initFromFile(filePath)) { return config; } return null; } - private static DisplayDeviceConfig getConfigFromGlobalXml(Context context) { - DisplayDeviceConfig config = new DisplayDeviceConfig(context); + private static DisplayDeviceConfig getConfigFromGlobalXml(Context context, + DisplayManagerFlags flags) { + DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags); config.initFromGlobalXml(); return config; } - private static DisplayDeviceConfig getConfigFromPmValues(Context context) { - DisplayDeviceConfig config = new DisplayDeviceConfig(context); + private static DisplayDeviceConfig getConfigFromPmValues(Context context, + DisplayManagerFlags flags) { + DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags); config.initFromDefaultValues(); return config; } @@ -1938,6 +1975,7 @@ public class DisplayDeviceConfig { loadScreenOffBrightnessSensorValueToLuxFromDdc(config); loadUsiVersion(config); mHdrBrightnessData = HdrBrightnessData.loadConfig(config); + loadBrightnessCapForWearBedtimeMode(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -1961,6 +1999,7 @@ public class DisplayDeviceConfig { loadAutoBrightnessConfigsFromConfigXml(); loadAutoBrightnessAvailableFromConfigXml(); loadRefreshRateSetting(null); + loadBrightnessCapForWearBedtimeModeFromConfigXml(); mLoadedFrom = "<config.xml>"; } @@ -2599,8 +2638,23 @@ public class DisplayDeviceConfig { * loading the value from the display config, and if not present, falls back to config.xml. */ private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) { - if (autoBrightnessConfig == null - || autoBrightnessConfig.getDisplayBrightnessMapping() == null) { + if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null + && autoBrightnessConfig.getLuxToBrightnessMapping() != null) { + LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping(); + final int size = mapping.getMap().getPoint().size(); + mBrightnessLevels = new float[size]; + mBrightnessLevelsLux = new float[size]; + for (int i = 0; i < size; i++) { + float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue(); + mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight); + mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst() + .floatValue(); + } + if (size > 0 && mBrightnessLevelsLux[0] != 0) { + throw new IllegalArgumentException( + "The first lux value in the display brightness mapping must be 0"); + } + } else { mBrightnessLevelsNits = getFloatArray(mContext.getResources() .obtainTypedArray(com.android.internal.R.array .config_autoBrightnessDisplayValuesNits), PowerManager @@ -2608,18 +2662,6 @@ public class DisplayDeviceConfig { mBrightnessLevelsLux = getLuxLevels(mContext.getResources() .getIntArray(com.android.internal.R.array .config_autoBrightnessLevels)); - } else { - final int size = autoBrightnessConfig.getDisplayBrightnessMapping() - .getDisplayBrightnessPoint().size(); - mBrightnessLevelsNits = new float[size]; - // The first control point is implicit and always at 0 lux. - mBrightnessLevelsLux = new float[size + 1]; - for (int i = 0; i < size; i++) { - mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping() - .getDisplayBrightnessPoint().get(i).getNits().floatValue(); - mBrightnessLevelsLux[i + 1] = autoBrightnessConfig.getDisplayBrightnessMapping() - .getDisplayBrightnessPoint().get(i).getLux().floatValue(); - } } } @@ -3350,6 +3392,23 @@ public class DisplayDeviceConfig { : null; } + private void loadBrightnessCapForWearBedtimeMode(DisplayConfiguration config) { + if (config != null) { + BigDecimal configBrightnessCap = config.getScreenBrightnessCapForWearBedtimeMode(); + if (configBrightnessCap != null) { + mBrightnessCapForWearBedtimeMode = configBrightnessCap.floatValue(); + } else { + loadBrightnessCapForWearBedtimeModeFromConfigXml(); + } + } + } + + private void loadBrightnessCapForWearBedtimeModeFromConfigXml() { + mBrightnessCapForWearBedtimeMode = BrightnessSynchronizer.brightnessIntToFloat( + mContext.getResources().getInteger(com.android.internal.R.integer + .config_screenBrightnessCapForWearBedtimeMode)); + } + /** * Container for high brightness mode configuration data. */ diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 6a7c17d54980..2ab15e639d68 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1853,7 +1853,7 @@ public final class DisplayManagerService extends SystemService { // early apps like SetupWizard/Launcher. In particular, SUW is displayed using // the virtual display inside VR before any VR-specific apps even run. mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext, - mHandler, mDisplayDeviceRepo); + mHandler, mDisplayDeviceRepo, mFlags); if (mVirtualDisplayAdapter != null) { registerDisplayAdapterLocked(mVirtualDisplayAdapter); } @@ -1871,7 +1871,7 @@ public final class DisplayManagerService extends SystemService { private void registerOverlayDisplayAdapterLocked() { registerDisplayAdapterLocked(new OverlayDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler)); + mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler, mFlags)); } private void registerWifiDisplayAdapterLocked() { @@ -1880,7 +1880,7 @@ public final class DisplayManagerService extends SystemService { || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) { mWifiDisplayAdapter = new WifiDisplayAdapter( mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, - mPersistentDataStore); + mPersistentDataStore, mFlags); registerDisplayAdapterLocked(mWifiDisplayAdapter); } } @@ -3288,8 +3288,10 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting static class Injector { VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, DisplayAdapter.Listener displayAdapterListener) { - return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener); + Handler handler, DisplayAdapter.Listener displayAdapterListener, + DisplayManagerFlags flags) { + return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, + flags); } LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index ff9a1ab61b13..22898a65c5de 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -84,8 +84,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final boolean mIsBootDisplayModeSupported; - private final DisplayManagerFlags mFlags; - private final DisplayNotificationManager mDisplayNotificationManager; private Context mOverlayContext; @@ -103,12 +101,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { Listener listener, DisplayManagerFlags flags, DisplayNotificationManager displayNotificationManager, Injector injector) { - super(syncRoot, context, handler, listener, TAG); + super(syncRoot, context, handler, listener, TAG, flags); mDisplayNotificationManager = displayNotificationManager; mInjector = injector; mSurfaceControlProxy = mInjector.getSurfaceControlProxy(); mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport(); - mFlags = flags; } @Override @@ -510,7 +507,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Load display device config final Context context = getOverlayContext(); mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId, - mIsFirstDisplay); + mIsFirstDisplay, getFeatureFlags()); // Load brightness HWC quirk mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk( @@ -831,7 +828,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { + ", state=" + Display.stateToString(state) + ")"); } - boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled(); + boolean isDisplayOffloadEnabled = + getFeatureFlags().isDisplayOffloadEnabled(); // We must tell sidekick/displayoffload to stop controlling the display // before we can change its power mode, so do that first. @@ -1377,8 +1375,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { } public DisplayDeviceConfig createDisplayDeviceConfig(Context context, - long physicalDisplayId, boolean isFirstDisplay) { - return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay); + long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) { + return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay, flags); } } diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 2ce7690ecc3f..22ff2d0eeffd 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -36,6 +36,7 @@ import android.view.SurfaceControl; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; import java.io.PrintWriter; @@ -134,8 +135,9 @@ final class OverlayDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, - Context context, Handler handler, Listener listener, Handler uiHandler) { - super(syncRoot, context, handler, listener, TAG); + Context context, Handler handler, Listener listener, Handler uiHandler, + DisplayManagerFlags featureFlags) { + super(syncRoot, context, handler, listener, TAG, featureFlags); mUiHandler = uiHandler; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index edbd42465534..ec5ad7de11b3 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -61,6 +61,7 @@ import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; @@ -88,7 +89,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, - Context context, Handler handler, Listener listener) { + Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) { this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { @Override public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) { @@ -99,14 +100,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter { public void destroyDisplay(IBinder displayToken) { DisplayControl.destroyDisplay(displayToken); } - }); + }, featureFlags); } @VisibleForTesting VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, - SurfaceControlDisplayFactory surfaceControlDisplayFactory) { - super(syncRoot, context, handler, listener, TAG); + SurfaceControlDisplayFactory surfaceControlDisplayFactory, + DisplayManagerFlags featureFlags) { + super(syncRoot, context, handler, listener, TAG, featureFlags); mHandler = handler; mSurfaceControlDisplayFactory = surfaceControlDisplayFactory; } diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 7660cf8a1c3a..aa98cd85d38e 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -41,6 +41,7 @@ import android.view.SurfaceControl; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; @@ -99,8 +100,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, - PersistentDataStore persistentDataStore) { - super(syncRoot, context, handler, listener, TAG); + PersistentDataStore persistentDataStore, DisplayManagerFlags featureFlags) { + super(syncRoot, context, handler, listener, TAG, featureFlags); if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) { throw new RuntimeException("WiFi display was requested, " diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index dfcda40d8e3c..8a884b6221a2 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -17,6 +17,7 @@ package com.android.server.display.brightness.clamper; import android.annotation.NonNull; +import android.os.Handler; import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; @@ -31,6 +32,18 @@ abstract class BrightnessClamper<T> { protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; protected boolean mIsActive = false; + @NonNull + protected final Handler mHandler; + + @NonNull + protected final BrightnessClamperController.ClamperChangeListener mChangeListener; + + BrightnessClamper(Handler handler, + BrightnessClamperController.ClamperChangeListener changeListener) { + mHandler = handler; + mChangeListener = changeListener; + } + float getBrightnessCap() { return mBrightnessCap; } @@ -60,6 +73,7 @@ abstract class BrightnessClamper<T> { protected enum Type { THERMAL, - POWER + POWER, + BEDTIME_MODE } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index b57491949196..765608e88356 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -90,7 +90,8 @@ public class BrightnessClamperController { } }; - mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags); + mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags, + context); mModifiers = injector.getModifiers(context); mOnPropertiesChangedListener = properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); @@ -234,7 +235,7 @@ public class BrightnessClamperController { List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler, ClamperChangeListener clamperChangeListener, DisplayDeviceData data, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, Context context) { List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>(); clampers.add( new BrightnessThermalClamper(handler, clamperChangeListener, data)); @@ -242,6 +243,10 @@ public class BrightnessClamperController { clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener, data)); } + if (flags.isBrightnessWearBedtimeModeClamperEnabled()) { + clampers.add(new BrightnessWearBedtimeModeClamper(handler, context, + clamperChangeListener, data)); + } return clampers; } @@ -257,7 +262,8 @@ public class BrightnessClamperController { * Config Data for clampers */ public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, - BrightnessPowerClamper.PowerData { + BrightnessPowerClamper.PowerData, + BrightnessWearBedtimeModeClamper.WearBedtimeModeData { @NonNull private final String mUniqueDisplayId; @NonNull @@ -315,5 +321,10 @@ public class BrightnessClamperController { public PowerThrottlingConfigData getPowerThrottlingConfigData() { return mDisplayDeviceConfig.getPowerThrottlingConfigData(); } + + @Override + public float getBrightnessWearBedtimeModeCap() { + return mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java index 339b5896f3f7..790322d75251 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java @@ -49,10 +49,6 @@ class BrightnessPowerClamper extends private final Injector mInjector; @NonNull private final DeviceConfigParameterProvider mConfigParameterProvider; - @NonNull - private final Handler mHandler; - @NonNull - private final ClamperChangeListener mChangeListener; @Nullable private PmicMonitor mPmicMonitor; // data from DeviceConfig, for all displays, for all dataSets @@ -99,10 +95,9 @@ class BrightnessPowerClamper extends @VisibleForTesting BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener, PowerData powerData) { + super(handler, listener); mInjector = injector; mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); - mHandler = handler; - mChangeListener = listener; mHandler.post(() -> { setDisplayData(powerData); diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java index 8ae962b83c69..944a8a65693b 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java @@ -54,10 +54,6 @@ class BrightnessThermalClamper extends private final IThermalService mThermalService; @NonNull private final DeviceConfigParameterProvider mConfigParameterProvider; - @NonNull - private final Handler mHandler; - @NonNull - private final ClamperChangeListener mChangelistener; // data from DeviceConfig, for all displays, for all dataSets // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData)) @NonNull @@ -108,10 +104,9 @@ class BrightnessThermalClamper extends @VisibleForTesting BrightnessThermalClamper(Injector injector, Handler handler, ClamperChangeListener listener, ThermalData thermalData) { + super(handler, listener); mThermalService = injector.getThermalService(); mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); - mHandler = handler; - mChangelistener = listener; mHandler.post(() -> { setDisplayData(thermalData); loadOverrideData(); @@ -220,7 +215,7 @@ class BrightnessThermalClamper extends if (brightnessCap != mBrightnessCap || mIsActive != isActive) { mBrightnessCap = brightnessCap; mIsActive = isActive; - mChangelistener.onChanged(); + mChangeListener.onChanged(); } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java new file mode 100644 index 000000000000..7e853bfc4ad6 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 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.brightness.clamper; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; + +public class BrightnessWearBedtimeModeClamper extends + BrightnessClamper<BrightnessWearBedtimeModeClamper.WearBedtimeModeData> { + + public static final int BEDTIME_MODE_OFF = 0; + public static final int BEDTIME_MODE_ON = 1; + + private final Context mContext; + + private final ContentObserver mSettingsObserver; + + BrightnessWearBedtimeModeClamper(Handler handler, Context context, + BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { + this(new Injector(), handler, context, listener, data); + } + + @VisibleForTesting + BrightnessWearBedtimeModeClamper(Injector injector, Handler handler, Context context, + BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { + super(handler, listener); + mContext = context; + mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); + mSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + final int bedtimeModeSetting = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.Wearable.BEDTIME_MODE, + BEDTIME_MODE_OFF); + mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON; + mChangeListener.onChanged(); + } + }; + injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver); + } + + @NonNull + @Override + Type getType() { + return Type.BEDTIME_MODE; + } + + @Override + void onDeviceConfigChanged() {} + + @Override + void onDisplayChanged(WearBedtimeModeData displayData) { + mHandler.post(() -> { + mBrightnessCap = displayData.getBrightnessWearBedtimeModeCap(); + mChangeListener.onChanged(); + }); + } + + @Override + void stop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + + interface WearBedtimeModeData { + float getBrightnessWearBedtimeModeCap(); + } + + @VisibleForTesting + static class Injector { + void registerBedtimeModeObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + cr.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE), + /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL); + } + } +} diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 5cfbf26338e3..c71f0cf2dd91 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -90,6 +90,14 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE, Flags::enableExternalVsyncProximityVote); + private final FlagState mBrightnessWearBedtimeModeClamperFlagState = new FlagState( + Flags.FLAG_BRIGHTNESS_WEAR_BEDTIME_MODE_CLAMPER, + Flags::brightnessWearBedtimeModeClamper); + + private final FlagState mAutoBrightnessModesFlagState = new FlagState( + Flags.FLAG_AUTO_BRIGHTNESS_MODES, + Flags::autoBrightnessModes); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -178,6 +186,17 @@ public class DisplayManagerFlags { return mVsyncProximityVote.isEnabled(); } + public boolean isBrightnessWearBedtimeModeClamperEnabled() { + return mBrightnessWearBedtimeModeClamperFlagState.isEnabled(); + } + + /** + * @return Whether generic auto-brightness modes are enabled + */ + public boolean areAutoBrightnessModesEnabled() { + return mAutoBrightnessModesFlagState.isEnabled(); + } + /** * dumps all flagstates * @param pw printWriter @@ -197,6 +216,8 @@ public class DisplayManagerFlags { pw.println(" " + mSmallAreaDetectionFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); pw.println(" " + mVsyncProximityVote); + pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState); + pw.println(" " + mAutoBrightnessModesFlagState); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index d95bdae7514d..9dfa1ee41ad6 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -120,3 +120,19 @@ flag { bug: "284866750" is_fixed_read_only: true } + +flag { + name: "brightness_wear_bedtime_mode_clamper" + namespace: "display_manager" + description: "Feature flag for the Wear Bedtime mode brightness clamper" + bug: "293613040" + is_fixed_read_only: true +} + +flag { + name: "auto_brightness_modes" + namespace: "display_manager" + description: "Feature flag for generic auto-brightness modes" + bug: "293613040" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 8eb03ec0d3bd..8fa838de3327 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -21,8 +21,8 @@ import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; import static android.view.Display.Mode.INVALID_MODE_ID; -import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRate; import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; +import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay; import android.annotation.IntegerRes; import android.annotation.NonNull; @@ -925,16 +925,11 @@ public class DisplayModeDirector { } @VisibleForTesting - DisplayObserver getDisplayObserver() { - return mDisplayObserver; - } - - @VisibleForTesting DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings( float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) { synchronized (mLock) { - mSettingsObserver.updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, - defaultRefreshRate, Display.DEFAULT_DISPLAY); + mSettingsObserver.updateRefreshRateSettingLocked( + minRefreshRate, peakRefreshRate, defaultRefreshRate); return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY); } } @@ -1268,23 +1263,9 @@ public class DisplayModeDirector { mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode); } - /** - * Update refresh rate settings for all displays - */ private void updateRefreshRateSettingLocked() { - Display[] displays = mInjector.getDisplays(); - for (int i = 0; i < displays.length; i++) { - updateRefreshRateSettingLocked(displays[i].getDisplayId()); - } - } - - /** - * Update refresh rate settings for a specific display - * @param displayId The display ID - */ - private void updateRefreshRateSettingLocked(int displayId) { final ContentResolver cr = mContext.getContentResolver(); - float highestRefreshRate = findHighestRefreshRate(mContext, displayId); + float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext); float minRefreshRate = Settings.System.getFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId()); @@ -1312,12 +1293,11 @@ public class DisplayModeDirector { } } - updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate, - displayId); + updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate); } - private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate, - float defaultRefreshRate, int displayId) { + private void updateRefreshRateSettingLocked( + float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) { // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is // used to predict if we're going to be doing frequent refresh rate switching, and if // so, enable the brightness observer. The logic here is more complicated and fragile @@ -1325,9 +1305,9 @@ public class DisplayModeDirector { Vote peakVote = peakRefreshRate == 0f ? null : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate)); - mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, + mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, peakVote); - mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, + mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY)); Vote defaultVote = defaultRefreshRate == 0f @@ -1484,8 +1464,7 @@ public class DisplayModeDirector { } } - @VisibleForTesting - public final class DisplayObserver implements DisplayManager.DisplayListener { + private final class DisplayObserver implements DisplayManager.DisplayListener { // Note that we can never call into DisplayManager or any of the non-POD classes it // returns, while holding mLock since it may call into DMS, which might be simultaneously // calling into us already holding its own lock. @@ -1577,7 +1556,6 @@ public class DisplayModeDirector { updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); - mSettingsObserver.updateRefreshRateSettingLocked(displayId); } @Nullable diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS new file mode 100644 index 000000000000..535a7509601c --- /dev/null +++ b/services/core/java/com/android/server/flags/OWNERS @@ -0,0 +1 @@ +per-file pinner.aconfig = edgararriaga@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig new file mode 100644 index 000000000000..606a6be29511 --- /dev/null +++ b/services/core/java/com/android/server/flags/pinner.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.flags" + +flag { + name: "pin_webview" + namespace: "system_performance" + description: "This flag controls if webview should be pinned in memory." + bug: "307594624" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 6f9b7d6328c7..27b01a5424b8 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -67,6 +67,7 @@ import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationRequest; import android.location.LocationResult; +import android.location.flags.Flags; import android.location.provider.ProviderProperties; import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; @@ -1048,10 +1049,22 @@ public class GnssLocationProvider extends AbstractLocationProvider implements stopBatching(); if (mStarted && mGnssNative.getCapabilities().hasScheduling()) { - // change period and/or lowPowerMode - if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC, - mFixInterval, mProviderRequest.isLowPower())) { - Log.e(TAG, "set_position_mode failed in updateRequirements"); + if (Flags.gnssCallStopBeforeSetPositionMode()) { + GnssPositionMode positionMode = new GnssPositionMode(mPositionMode, + GNSS_POSITION_RECURRENCE_PERIODIC, mFixInterval, + /* preferredAccuracy= */ 0, + /* preferredTime= */ 0, + mProviderRequest.isLowPower()); + if (!positionMode.equals(mLastPositionMode)) { + stopNavigating(); + startNavigating(); + } + } else { + // change period and/or lowPowerMode + if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC, + mFixInterval, mProviderRequest.isLowPower())) { + Log.e(TAG, "set_position_mode failed in updateRequirements"); + } } } else if (!mStarted) { // start GPS @@ -1234,11 +1247,32 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } int interval = mGnssNative.getCapabilities().hasScheduling() ? mFixInterval : 1000; - if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC, - interval, mProviderRequest.isLowPower())) { - setStarted(false); - Log.e(TAG, "set_position_mode failed in startNavigating()"); - return; + + if (Flags.gnssCallStopBeforeSetPositionMode()) { + boolean success = mGnssNative.setPositionMode(mPositionMode, + GNSS_POSITION_RECURRENCE_PERIODIC, interval, + /* preferredAccuracy= */ 0, + /* preferredTime= */ 0, + mProviderRequest.isLowPower()); + if (success) { + mLastPositionMode = new GnssPositionMode(mPositionMode, + GNSS_POSITION_RECURRENCE_PERIODIC, interval, + /* preferredAccuracy= */ 0, + /* preferredTime= */ 0, + mProviderRequest.isLowPower()); + } else { + mLastPositionMode = null; + setStarted(false); + Log.e(TAG, "set_position_mode failed in startNavigating()"); + return; + } + } else { + if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC, + interval, mProviderRequest.isLowPower())) { + setStarted(false); + Log.e(TAG, "set_position_mode failed in startNavigating()"); + return; + } } if (!mGnssNative.start()) { setStarted(false); diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java deleted file mode 100644 index 8cb334dc2260..000000000000 --- a/services/core/java/com/android/server/media/AudioAttributesUtils.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2023 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.media; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.media.AudioAttributes; -import android.media.AudioDeviceAttributes; -import android.media.AudioDeviceInfo; -import android.media.MediaRoute2Info; - -import com.android.media.flags.Flags; - -/* package */ final class AudioAttributesUtils { - - /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .build(); - - private AudioAttributesUtils() { - // no-op to prevent instantiation. - } - - @MediaRoute2Info.Type - /* package */ static int mapToMediaRouteType( - @NonNull AudioDeviceAttributes audioDeviceAttributes) { - if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { - switch (audioDeviceAttributes.getType()) { - case AudioDeviceInfo.TYPE_HDMI_ARC: - return MediaRoute2Info.TYPE_HDMI_ARC; - case AudioDeviceInfo.TYPE_HDMI_EARC: - return MediaRoute2Info.TYPE_HDMI_EARC; - } - } - switch (audioDeviceAttributes.getType()) { - case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: - case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: - return MediaRoute2Info.TYPE_BUILTIN_SPEAKER; - case AudioDeviceInfo.TYPE_WIRED_HEADSET: - return MediaRoute2Info.TYPE_WIRED_HEADSET; - case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: - return MediaRoute2Info.TYPE_WIRED_HEADPHONES; - case AudioDeviceInfo.TYPE_DOCK: - case AudioDeviceInfo.TYPE_DOCK_ANALOG: - return MediaRoute2Info.TYPE_DOCK; - case AudioDeviceInfo.TYPE_HDMI: - case AudioDeviceInfo.TYPE_HDMI_ARC: - case AudioDeviceInfo.TYPE_HDMI_EARC: - return MediaRoute2Info.TYPE_HDMI; - case AudioDeviceInfo.TYPE_USB_DEVICE: - return MediaRoute2Info.TYPE_USB_DEVICE; - case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: - return MediaRoute2Info.TYPE_BLUETOOTH_A2DP; - case AudioDeviceInfo.TYPE_BLE_HEADSET: - return MediaRoute2Info.TYPE_BLE_HEADSET; - case AudioDeviceInfo.TYPE_HEARING_AID: - return MediaRoute2Info.TYPE_HEARING_AID; - default: - return MediaRoute2Info.TYPE_UNKNOWN; - } - } - - /* package */ static boolean isDeviceOutputAttributes( - @Nullable AudioDeviceAttributes audioDeviceAttributes) { - if (audioDeviceAttributes == null) { - return false; - } - - if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { - return false; - } - - switch (audioDeviceAttributes.getType()) { - case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: - case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: - case AudioDeviceInfo.TYPE_WIRED_HEADSET: - case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: - case AudioDeviceInfo.TYPE_DOCK: - case AudioDeviceInfo.TYPE_DOCK_ANALOG: - case AudioDeviceInfo.TYPE_HDMI: - case AudioDeviceInfo.TYPE_HDMI_ARC: - case AudioDeviceInfo.TYPE_HDMI_EARC: - case AudioDeviceInfo.TYPE_USB_DEVICE: - return true; - default: - return false; - } - } - - /* package */ static boolean isBluetoothOutputAttributes( - @Nullable AudioDeviceAttributes audioDeviceAttributes) { - if (audioDeviceAttributes == null) { - return false; - } - - if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { - return false; - } - - switch (audioDeviceAttributes.getType()) { - case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: - case AudioDeviceInfo.TYPE_BLE_HEADSET: - case AudioDeviceInfo.TYPE_BLE_SPEAKER: - case AudioDeviceInfo.TYPE_HEARING_AID: - return true; - default: - return false; - } - } - -} diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java index 8bc69c226d1a..a00999d08b5b 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java @@ -17,7 +17,6 @@ package com.android.server.media; import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; -import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,38 +30,37 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.media.AudioManager; -import android.media.AudioSystem; import android.media.MediaRoute2Info; import android.os.UserHandle; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; -import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; /** - * Controls bluetooth routes and provides selected route override. + * Maintains a list of connected {@link BluetoothDevice bluetooth devices} and allows their + * activation. * - * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does - * not support routes selection logic. Instead, relies on external clients to make a decision - * about currently selected route. - * - * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio - * Policies. + * <p>This class also serves as ground truth for assigning {@link MediaRoute2Info#getId() route ids} + * for bluetooth routes via {@link #getRouteIdForBluetoothAddress}. */ -/* package */ class AudioPoliciesBluetoothRouteController - implements BluetoothRouteController { - private static final String TAG = "APBtRouteController"; +// TODO: b/305199571 - Rename this class to remove the RouteController suffix, which causes +// confusion with the BluetoothRouteController interface. +/* package */ class AudioPoliciesBluetoothRouteController { + private static final String TAG = SystemMediaRoute2Provider.TAG; private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_"; @@ -75,11 +73,8 @@ import java.util.Set; private final DeviceStateChangedReceiver mDeviceStateChangedReceiver = new DeviceStateChangedReceiver(); - @NonNull - private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); - - @NonNull - private final SparseIntArray mVolumeMap = new SparseIntArray(); + @NonNull private Map<String, BluetoothDevice> mAddressToBondedDevice = new HashMap<>(); + @NonNull private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); @NonNull private final Context mContext; @@ -89,11 +84,6 @@ import java.util.Set; private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener; @NonNull private final BluetoothProfileMonitor mBluetoothProfileMonitor; - @NonNull - private final AudioManager mAudioManager; - - @Nullable - private BluetoothRouteInfo mSelectedBluetoothRoute; AudioPoliciesBluetoothRouteController(@NonNull Context context, @NonNull BluetoothAdapter bluetoothAdapter, @@ -107,21 +97,12 @@ import java.util.Set; @NonNull BluetoothAdapter bluetoothAdapter, @NonNull BluetoothProfileMonitor bluetoothProfileMonitor, @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) { - Objects.requireNonNull(context); - Objects.requireNonNull(bluetoothAdapter); - Objects.requireNonNull(bluetoothProfileMonitor); - Objects.requireNonNull(listener); - - mContext = context; - mBluetoothAdapter = bluetoothAdapter; - mBluetoothProfileMonitor = bluetoothProfileMonitor; - mAudioManager = mContext.getSystemService(AudioManager.class); - mListener = listener; - - updateBluetoothRoutes(); + mContext = Objects.requireNonNull(context); + mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); + mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor); + mListener = Objects.requireNonNull(listener); } - @Override public void start(UserHandle user) { mBluetoothProfileMonitor.start(); @@ -133,122 +114,63 @@ import java.util.Set; IntentFilter deviceStateChangedIntentFilter = new IntentFilter(); - deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); deviceStateChangedIntentFilter.addAction( BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); deviceStateChangedIntentFilter.addAction( BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); - deviceStateChangedIntentFilter.addAction( - BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user, deviceStateChangedIntentFilter, null, null); + updateBluetoothRoutes(); } - @Override public void stop() { mContext.unregisterReceiver(mAdapterStateChangedReceiver); mContext.unregisterReceiver(mDeviceStateChangedReceiver); } - @Override - public boolean selectRoute(@Nullable String deviceAddress) { - synchronized (this) { - // Fetch all available devices in order to avoid race conditions with Bluetooth stack. - updateBluetoothRoutes(); - - if (deviceAddress == null) { - mSelectedBluetoothRoute = null; - return true; - } - - BluetoothRouteInfo bluetoothRouteInfo = mBluetoothRoutes.get(deviceAddress); - - if (bluetoothRouteInfo == null) { - Slog.w(TAG, "Cannot find bluetooth route for " + deviceAddress); - return false; - } - - mSelectedBluetoothRoute = bluetoothRouteInfo; - setRouteConnectionState(mSelectedBluetoothRoute, STATE_CONNECTED); - - updateConnectivityStateForDevicesInTheSameGroup(); - - return true; - } - } - - /** - * Updates connectivity state for devices in the same devices group. - * - * <p>{@link BluetoothProfile#LE_AUDIO} and {@link BluetoothProfile#HEARING_AID} support - * grouping devices. Devices that belong to the same group should have the same routeId but - * different physical address. - * - * <p>In case one of the devices from the group is selected then other devices should also - * reflect this by changing their connectivity status to - * {@link MediaRoute2Info#CONNECTION_STATE_CONNECTED}. - */ - private void updateConnectivityStateForDevicesInTheSameGroup() { - synchronized (this) { - for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { - if (TextUtils.equals(btRoute.mRoute.getId(), mSelectedBluetoothRoute.mRoute.getId()) - && !TextUtils.equals(btRoute.mBtDevice.getAddress(), - mSelectedBluetoothRoute.mBtDevice.getAddress())) { - setRouteConnectionState(btRoute, STATE_CONNECTED); - } - } - } + @Nullable + public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) { + BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address); + // TODO: b/305199571 - Optimize the following statement to avoid creating the full + // MediaRoute2Info instance. We just need the id. + return bluetoothDevice != null + ? createBluetoothRoute(bluetoothDevice).mRoute.getId() + : null; } - @Override - public void transferTo(@Nullable String routeId) { - if (routeId == null) { - mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO); - return; - } - - BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId); + public synchronized void activateBluetoothDeviceWithAddress(String address) { + BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(address); if (btRouteInfo == null) { - Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId); + Slog.w(TAG, "activateBluetoothDeviceWithAddress: Ignoring unknown address " + address); return; } - mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO); } - @Nullable - private BluetoothRouteInfo findBluetoothRouteWithRouteId(@Nullable String routeId) { - if (routeId == null) { - return null; - } - synchronized (this) { - for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) { - if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) { - return btRouteInfo; - } - } - } - return null; - } - private void updateBluetoothRoutes() { Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); - if (bondedDevices == null) { - return; - } - synchronized (this) { mBluetoothRoutes.clear(); - - // We need to query all available to BT stack devices in order to avoid inconsistency - // between external services, like, AndroidManager, and BT stack. + if (bondedDevices == null) { + // Bonded devices is null upon running into a BluetoothAdapter error. + Log.w(TAG, "BluetoothAdapter.getBondedDevices returned null."); + return; + } + // We don't clear bonded devices if we receive a null getBondedDevices result, because + // that probably means that the bluetooth stack ran into an issue. Not that all devices + // have been unpaired. + mAddressToBondedDevice = + bondedDevices.stream() + .collect( + Collectors.toMap( + BluetoothDevice::getAddress, Function.identity())); for (BluetoothDevice device : bondedDevices) { - if (isDeviceConnected(device)) { + if (device.isConnected()) { BluetoothRouteInfo newBtRoute = createBluetoothRoute(device); if (newBtRoute.mConnectedProfiles.size() > 0) { mBluetoothRoutes.put(device.getAddress(), newBtRoute); @@ -258,106 +180,51 @@ import java.util.Set; } } - @VisibleForTesting - /* package */ boolean isDeviceConnected(@NonNull BluetoothDevice device) { - return device.isConnected(); - } - - @Nullable - @Override - public MediaRoute2Info getSelectedRoute() { - synchronized (this) { - if (mSelectedBluetoothRoute == null) { - return null; - } - - return mSelectedBluetoothRoute.mRoute; - } - } - @NonNull - @Override - public List<MediaRoute2Info> getTransferableRoutes() { - List<MediaRoute2Info> routes = getAllBluetoothRoutes(); - synchronized (this) { - if (mSelectedBluetoothRoute != null) { - routes.remove(mSelectedBluetoothRoute.mRoute); - } - } - return routes; - } - - @NonNull - @Override - public List<MediaRoute2Info> getAllBluetoothRoutes() { + public List<MediaRoute2Info> getAvailableBluetoothRoutes() { List<MediaRoute2Info> routes = new ArrayList<>(); - List<String> routeIds = new ArrayList<>(); - - MediaRoute2Info selectedRoute = getSelectedRoute(); - if (selectedRoute != null) { - routes.add(selectedRoute); - routeIds.add(selectedRoute.getId()); - } + Set<String> routeIds = new HashSet<>(); synchronized (this) { for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { - // A pair of hearing aid devices or having the same hardware address - if (routeIds.contains(btRoute.mRoute.getId())) { - continue; + // See createBluetoothRoute for info on why we do this. + if (routeIds.add(btRoute.mRoute.getId())) { + routes.add(btRoute.mRoute); } - routes.add(btRoute.mRoute); - routeIds.add(btRoute.mRoute.getId()); } } return routes; } - @Override - public boolean updateVolumeForDevices(int devices, int volume) { - int routeType; - if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) { - routeType = MediaRoute2Info.TYPE_HEARING_AID; - } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP - | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES - | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { - routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP; - } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) { - routeType = MediaRoute2Info.TYPE_BLE_HEADSET; - } else { - return false; - } - - synchronized (this) { - mVolumeMap.put(routeType, volume); - if (mSelectedBluetoothRoute == null - || mSelectedBluetoothRoute.mRoute.getType() != routeType) { - return false; - } - - mSelectedBluetoothRoute.mRoute = - new MediaRoute2Info.Builder(mSelectedBluetoothRoute.mRoute) - .setVolume(volume) - .build(); - } - - notifyBluetoothRoutesUpdated(); - return true; - } - private void notifyBluetoothRoutesUpdated() { mListener.onBluetoothRoutesUpdated(); } + /** + * Creates a new {@link BluetoothRouteInfo}, including its member {@link + * BluetoothRouteInfo#mRoute}. + * + * <p>The most important logic in this method is around the {@link MediaRoute2Info#getId() route + * id} assignment. In some cases we want to group multiple {@link BluetoothDevice bluetooth + * devices} as a single media route. For example, the left and right hearing aids get exposed as + * two different BluetoothDevice instances, but we want to show them as a single route. In this + * case, we assign the same route id to all "group" bluetooth devices (like left and right + * hearing aids), so that a single route is exposed for both of them. + * + * <p>Deduplication by id happens downstream because we need to be able to refer to all + * bluetooth devices individually, since the audio stack refers to a bluetooth device group by + * any of its member devices. + */ private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); newBtRoute.mBtDevice = device; - - String routeId = device.getAddress(); String deviceName = device.getName(); if (TextUtils.isEmpty(deviceName)) { deviceName = mContext.getResources().getText(R.string.unknownName).toString(); } + + String routeId = device.getAddress(); int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP; newBtRoute.mConnectedProfiles = new SparseBooleanArray(); if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) { @@ -365,7 +232,6 @@ import java.util.Set; } if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) { newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true); - // Intentionally assign the same ID for a pair of devices to publish only one of them. routeId = HEARING_AID_ROUTE_ID_PREFIX + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.HEARING_AID, device); type = MediaRoute2Info.TYPE_HEARING_AID; @@ -377,66 +243,27 @@ import java.util.Set; type = MediaRoute2Info.TYPE_BLE_HEADSET; } - // Current volume will be set when connected. - newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName) - .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) - .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK) - .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) - .setDescription(mContext.getResources().getText( - R.string.bluetooth_a2dp_audio_route_name).toString()) - .setType(type) - .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) - .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) - .setAddress(device.getAddress()) - .build(); + // Note that volume is only relevant for active bluetooth routes, and those are managed via + // AudioManager. + newBtRoute.mRoute = + new MediaRoute2Info.Builder(routeId, deviceName) + .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) + .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) + .setDescription( + mContext.getResources() + .getText(R.string.bluetooth_a2dp_audio_route_name) + .toString()) + .setType(type) + .setAddress(device.getAddress()) + .build(); return newBtRoute; } - private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute, - @MediaRoute2Info.ConnectionState int state) { - if (btRoute == null) { - Slog.w(TAG, "setRouteConnectionState: route shouldn't be null"); - return; - } - if (btRoute.mRoute.getConnectionState() == state) { - return; - } - - MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute) - .setConnectionState(state); - builder.setType(btRoute.getRouteType()); - - - - if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) { - int currentVolume; - synchronized (this) { - currentVolume = mVolumeMap.get(btRoute.getRouteType(), 0); - } - builder.setVolume(currentVolume); - } - - btRoute.mRoute = builder.build(); - } - private static class BluetoothRouteInfo { private BluetoothDevice mBtDevice; private MediaRoute2Info mRoute; private SparseBooleanArray mConnectedProfiles; - - @MediaRoute2Info.Type - int getRouteType() { - // Let hearing aid profile have a priority. - if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) { - return MediaRoute2Info.TYPE_HEARING_AID; - } - - if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) { - return MediaRoute2Info.TYPE_BLE_HEADSET; - } - - return MediaRoute2Info.TYPE_BLUETOOTH_A2DP; - } } private class AdapterStateChangedReceiver extends BroadcastReceiver { @@ -468,9 +295,6 @@ import java.util.Set; @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { - case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: - case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: - case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 6bdfae2dc02f..27df00f30531 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -17,228 +17,590 @@ package com.android.server.media; import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; -import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; -import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; -import static android.media.MediaRoute2Info.TYPE_DOCK; -import static android.media.MediaRoute2Info.TYPE_HDMI; -import static android.media.MediaRoute2Info.TYPE_HDMI_ARC; -import static android.media.MediaRoute2Info.TYPE_HDMI_EARC; -import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; -import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; -import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceCallback; +import android.media.AudioDeviceInfo; import android.media.AudioManager; -import android.media.AudioRoutesInfo; -import android.media.IAudioRoutesObserver; -import android.media.IAudioService; import android.media.MediaRoute2Info; -import android.os.RemoteException; +import android.media.audiopolicy.AudioProductStrategy; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.os.UserHandle; +import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; +import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; +/** + * Maintains a list of all available routes and supports transfers to any of them. + * + * <p>This implementation is intended for use in conjunction with {@link + * NoOpBluetoothRouteController}, as it manages bluetooth devices directly. + * + * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the + * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes + * which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the + * bluetooth stack (for example {@link BluetoothAdapter}. + */ +// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged +// by the audio team as a confusing name. /* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController { + private static final String TAG = SystemMediaRoute2Provider.TAG; - private static final String TAG = "APDeviceRoutesController"; - - @NonNull - private final Context mContext; @NonNull - private final AudioManager mAudioManager; - @NonNull - private final IAudioService mAudioService; + private static final AudioAttributes MEDIA_USAGE_AUDIO_ATTRIBUTES = + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); @NonNull - private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; - @NonNull - private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); + private static final SparseArray<SystemRouteInfo> AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO = + new SparseArray<>(); - private int mDeviceVolume; + @NonNull private final Context mContext; + @NonNull private final AudioManager mAudioManager; + @NonNull private final Handler mHandler; + @NonNull private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + @NonNull private final AudioPoliciesBluetoothRouteController mBluetoothRouteController; @NonNull - private MediaRoute2Info mDeviceRoute; - @Nullable - private MediaRoute2Info mSelectedRoute; + private final Map<String, MediaRoute2InfoHolder> mRouteIdToAvailableDeviceRoutes = + new HashMap<>(); + + @NonNull private final AudioProductStrategy mStrategyForMedia; - @VisibleForTesting - /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context, + @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl(); + + @NonNull + private final AudioManager.OnDevicesForAttributesChangedListener + mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener; + + @NonNull private MediaRoute2Info mSelectedRoute; + + // TODO: b/305199571 - Support nullable btAdapter and strategyForMedia which, when null, means + // no support for transferring to inactive bluetooth routes and transferring to any routes + // respectively. + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + /* package */ AudioPoliciesDeviceRouteController( + @NonNull Context context, @NonNull AudioManager audioManager, - @NonNull IAudioService audioService, + @NonNull Looper looper, + @NonNull AudioProductStrategy strategyForMedia, + @NonNull BluetoothAdapter btAdapter, @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { - Objects.requireNonNull(context); - Objects.requireNonNull(audioManager); - Objects.requireNonNull(audioService); - Objects.requireNonNull(onDeviceRouteChangedListener); + mContext = Objects.requireNonNull(context); + mAudioManager = Objects.requireNonNull(audioManager); + mHandler = new Handler(Objects.requireNonNull(looper)); + mStrategyForMedia = Objects.requireNonNull(strategyForMedia); + mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener); + mBluetoothRouteController = + new AudioPoliciesBluetoothRouteController( + mContext, btAdapter, this::rebuildAvailableRoutes); + rebuildAvailableRoutes(); + } - mContext = context; - mOnDeviceRouteChangedListener = onDeviceRouteChangedListener; + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + @Override + public void start(UserHandle mUser) { + mBluetoothRouteController.start(mUser); + mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, mHandler); + mAudioManager.addOnDevicesForAttributesChangedListener( + AudioRoutingUtils.ATTRIBUTES_MEDIA, + new HandlerExecutor(mHandler), + mOnDevicesForAttributesChangedListener); + } - mAudioManager = audioManager; - mAudioService = audioService; + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + @Override + public void stop() { + mAudioManager.removeOnDevicesForAttributesChangedListener( + mOnDevicesForAttributesChangedListener); + mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); + mBluetoothRouteController.stop(); + mHandler.removeCallbacksAndMessages(/* token= */ null); + } - AudioRoutesInfo newAudioRoutes = null; - try { - newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); - } catch (RemoteException e) { - Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e); - } + @Override + @NonNull + public synchronized MediaRoute2Info getSelectedRoute() { + return mSelectedRoute; + } - mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes); + @Override + @NonNull + public synchronized List<MediaRoute2Info> getAvailableRoutes() { + return mRouteIdToAvailableDeviceRoutes.values().stream() + .map(it -> it.mMediaRoute2Info) + .toList(); } + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) @Override - public synchronized boolean selectRoute(@Nullable Integer type) { - if (type == null) { - mSelectedRoute = null; - return true; + public synchronized void transferTo(@Nullable String routeId) { + if (routeId == null) { + // This should never happen: This branch should only execute when the matching bluetooth + // route controller is not the no-op one. + // TODO: b/305199571 - Make routeId non-null and remove this branch once we remove the + // legacy route controller implementations. + Slog.e(TAG, "Unexpected call to AudioPoliciesDeviceRouteController#transferTo(null)"); + return; } - - if (!isDeviceRouteType(type)) { - return false; + MediaRoute2InfoHolder mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId); + if (mediaRoute2InfoHolder == null) { + Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId); + return; + } + if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) { + // By default, the last connected device is the active route so we don't need to apply a + // routing audio policy. + mBluetoothRouteController.activateBluetoothDeviceWithAddress( + mediaRoute2InfoHolder.mMediaRoute2Info.getAddress()); + mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia); + } else { + AudioDeviceAttributes attr = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + mediaRoute2InfoHolder.mAudioDeviceInfoType, + /* address= */ ""); // This is not a BT device, hence no address needed. + mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr); } + } - mSelectedRoute = createRouteFromAudioInfo(type); + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + @Override + public synchronized boolean updateVolume(int volume) { + // TODO: b/305199571 - Optimize so that we only update the volume of the selected route. We + // don't need to rebuild all available routes. + rebuildAvailableRoutes(); return true; } - @Override - @NonNull - public synchronized MediaRoute2Info getSelectedRoute() { - if (mSelectedRoute != null) { - return mSelectedRoute; + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + private void onDevicesForAttributesChangedListener( + AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes) { + if (attributes.getUsage() == AudioAttributes.USAGE_MEDIA) { + // We only care about the media usage. Ignore everything else. + rebuildAvailableRoutes(); } - return mDeviceRoute; } - @Override - public synchronized boolean updateVolume(int volume) { - if (mDeviceVolume == volume) { - return false; + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + private synchronized void rebuildAvailableRoutes() { + List<AudioDeviceAttributes> attributesOfSelectedOutputDevices = + mAudioManager.getDevicesForAttributes(MEDIA_USAGE_AUDIO_ATTRIBUTES); + int selectedDeviceAttributesType; + if (attributesOfSelectedOutputDevices.isEmpty()) { + Slog.e( + TAG, + "Unexpected empty list of output devices for media. Using built-in speakers."); + selectedDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + } else { + if (attributesOfSelectedOutputDevices.size() > 1) { + Slog.w( + TAG, + "AudioManager.getDevicesForAttributes returned more than one element. Using" + + " the first one."); + } + selectedDeviceAttributesType = attributesOfSelectedOutputDevices.get(0).getType(); } - mDeviceVolume = volume; - - if (mSelectedRoute != null) { - mSelectedRoute = new MediaRoute2Info.Builder(mSelectedRoute) - .setVolume(volume) - .build(); + AudioDeviceInfo[] audioDeviceInfos = + mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + mRouteIdToAvailableDeviceRoutes.clear(); + MediaRoute2InfoHolder newSelectedRouteHolder = null; + for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) { + MediaRoute2Info mediaRoute2Info = + createMediaRoute2InfoFromAudioDeviceInfo(audioDeviceInfo); + // Null means audioDeviceInfo is not a supported media output, like a phone's builtin + // earpiece. We ignore those. + if (mediaRoute2Info != null) { + int audioDeviceInfoType = audioDeviceInfo.getType(); + MediaRoute2InfoHolder newHolder = + MediaRoute2InfoHolder.createForAudioManagerRoute( + mediaRoute2Info, audioDeviceInfoType); + mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder); + if (selectedDeviceAttributesType == audioDeviceInfoType) { + newSelectedRouteHolder = newHolder; + } + } } - mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) - .setVolume(volume) - .build(); + if (mRouteIdToAvailableDeviceRoutes.isEmpty()) { + // Due to an unknown reason (possibly an audio server crash), we ended up with an empty + // list of routes. Our entire codebase assumes at least one system route always exists, + // so we create a placeholder route represented as a built-in speaker for + // user-presentation purposes. + Slog.e(TAG, "Ended up with an empty list of routes. Creating a placeholder route."); + MediaRoute2InfoHolder placeholderRouteHolder = createPlaceholderBuiltinSpeakerRoute(); + String placeholderRouteId = placeholderRouteHolder.mMediaRoute2Info.getId(); + mRouteIdToAvailableDeviceRoutes.put(placeholderRouteId, placeholderRouteHolder); + } - return true; + if (newSelectedRouteHolder == null) { + Slog.e( + TAG, + "Could not map this selected device attribute type to an available route: " + + selectedDeviceAttributesType); + // We know mRouteIdToAvailableDeviceRoutes is not empty. + newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next(); + } + MediaRoute2InfoHolder selectedRouteHolderWithUpdatedVolumeInfo = + newSelectedRouteHolder.copyWithVolumeInfoFromAudioManager(mAudioManager); + mRouteIdToAvailableDeviceRoutes.put( + newSelectedRouteHolder.mMediaRoute2Info.getId(), + selectedRouteHolderWithUpdatedVolumeInfo); + mSelectedRoute = selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info; + + // We only add those BT routes that we have not already obtained from audio manager (which + // are active). + mBluetoothRouteController.getAvailableBluetoothRoutes().stream() + .filter(it -> !mRouteIdToAvailableDeviceRoutes.containsKey(it.getId())) + .map(MediaRoute2InfoHolder::createForInactiveBluetoothRoute) + .forEach( + it -> mRouteIdToAvailableDeviceRoutes.put(it.mMediaRoute2Info.getId(), it)); + mOnDeviceRouteChangedListener.onDeviceRouteChanged(); } - @NonNull - private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) { - int type = TYPE_BUILTIN_SPEAKER; - - if (newRoutes != null) { - if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) { - type = TYPE_WIRED_HEADPHONES; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { - type = TYPE_WIRED_HEADSET; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { - type = TYPE_DOCK; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { - type = TYPE_HDMI; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { - type = TYPE_USB_DEVICE; - } - } + private MediaRoute2InfoHolder createPlaceholderBuiltinSpeakerRoute() { + int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + return MediaRoute2InfoHolder.createForAudioManagerRoute( + createMediaRoute2Info( + /* routeId= */ null, type, /* productName= */ null, /* address= */ null), + type); + } - return createRouteFromAudioInfo(type); + @Nullable + private MediaRoute2Info createMediaRoute2InfoFromAudioDeviceInfo( + AudioDeviceInfo audioDeviceInfo) { + String address = audioDeviceInfo.getAddress(); + // Passing a null route id means we want to get the default id for the route. Generally, we + // only expect to pass null for non-Bluetooth routes. + String routeId = + TextUtils.isEmpty(address) + ? null + : mBluetoothRouteController.getRouteIdForBluetoothAddress(address); + return createMediaRoute2Info( + routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address); } - @NonNull - private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) { - int name = R.string.default_audio_route_name; - switch (type) { - case TYPE_WIRED_HEADPHONES: - case TYPE_WIRED_HEADSET: - name = R.string.default_audio_route_name_headphones; - break; - case TYPE_DOCK: - name = R.string.default_audio_route_name_dock_speakers; - break; - case TYPE_HDMI: - case TYPE_HDMI_ARC: - case TYPE_HDMI_EARC: - name = R.string.default_audio_route_name_external_device; - break; - case TYPE_USB_DEVICE: - name = R.string.default_audio_route_name_usb; - break; - } - - synchronized (this) { - return new MediaRoute2Info.Builder( - MediaRoute2Info.ROUTE_ID_DEVICE, - mContext.getResources().getText(name).toString()) - .setVolumeHandling( - mAudioManager.isVolumeFixed() - ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED - : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) - .setVolume(mDeviceVolume) - .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) - .setType(type) - .addFeature(FEATURE_LIVE_AUDIO) - .addFeature(FEATURE_LIVE_VIDEO) - .addFeature(FEATURE_LOCAL_PLAYBACK) - .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) - .build(); + /** + * Creates a new {@link MediaRoute2Info} using the provided information. + * + * @param routeId A route id, or null to use an id pre-defined for the given {@code type}. + * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}. + * @param productName The product name as obtained from {@link + * AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code + * type}. + * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link + * BluetoothDevice#getAddress()}. + * @return The new {@link MediaRoute2Info}. + */ + @Nullable + private MediaRoute2Info createMediaRoute2Info( + @Nullable String routeId, + int audioDeviceInfoType, + @Nullable CharSequence productName, + @Nullable String address) { + SystemRouteInfo systemRouteInfo = + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType); + if (systemRouteInfo == null) { + // Device type that's intentionally unsupported for media output, like the built-in + // earpiece. + return null; + } + CharSequence humanReadableName = productName; + if (TextUtils.isEmpty(humanReadableName)) { + humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource); + } + if (routeId == null) { + // The caller hasn't provided an id, so we use a pre-defined one. This happens when we + // are creating a non-BT route, or we are creating a BT route but a race condition + // caused AudioManager to expose the BT route before BluetoothAdapter, preventing us + // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress. + routeId = systemRouteInfo.mDefaultRouteId; } + return new MediaRoute2Info.Builder(routeId, humanReadableName) + .setType(systemRouteInfo.mMediaRoute2InfoType) + .setAddress(address) + .setSystemRoute(true) + .addFeature(FEATURE_LIVE_AUDIO) + .addFeature(FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) + .build(); } /** - * Checks if the given type is a device route. - * - * <p>Device route means a route which is either built-in or wired to the current device. - * - * @param type specifies the type of the device. - * @return {@code true} if the device is wired or built-in and {@code false} otherwise. + * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the + * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this + * class. */ - private boolean isDeviceRouteType(@MediaRoute2Info.Type int type) { - switch (type) { - case TYPE_BUILTIN_SPEAKER: - case TYPE_WIRED_HEADPHONES: - case TYPE_WIRED_HEADSET: - case TYPE_DOCK: - case TYPE_HDMI: - case TYPE_HDMI_ARC: - case TYPE_HDMI_EARC: - case TYPE_USB_DEVICE: - return true; - default: - return false; + private static class MediaRoute2InfoHolder { + + public final MediaRoute2Info mMediaRoute2Info; + public final int mAudioDeviceInfoType; + public final boolean mCorrespondsToInactiveBluetoothRoute; + + public static MediaRoute2InfoHolder createForAudioManagerRoute( + MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType) { + return new MediaRoute2InfoHolder( + mediaRoute2Info, + audioDeviceInfoType, + /* correspondsToInactiveBluetoothRoute= */ false); + } + + public static MediaRoute2InfoHolder createForInactiveBluetoothRoute( + MediaRoute2Info mediaRoute2Info) { + // There's no corresponding audio device info, hence the audio device info type is + // unknown. + return new MediaRoute2InfoHolder( + mediaRoute2Info, + /* audioDeviceInfoType= */ AudioDeviceInfo.TYPE_UNKNOWN, + /* correspondsToInactiveBluetoothRoute= */ true); + } + + private MediaRoute2InfoHolder( + MediaRoute2Info mediaRoute2Info, + int audioDeviceInfoType, + boolean correspondsToInactiveBluetoothRoute) { + mMediaRoute2Info = mediaRoute2Info; + mAudioDeviceInfoType = audioDeviceInfoType; + mCorrespondsToInactiveBluetoothRoute = correspondsToInactiveBluetoothRoute; + } + + public MediaRoute2InfoHolder copyWithVolumeInfoFromAudioManager( + AudioManager mAudioManager) { + MediaRoute2Info routeInfoWithVolumeInfo = + new MediaRoute2Info.Builder(mMediaRoute2Info) + .setVolumeHandling( + mAudioManager.isVolumeFixed() + ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED + : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) + .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) + .setVolumeMax( + mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .build(); + return new MediaRoute2InfoHolder( + routeInfoWithVolumeInfo, + mAudioDeviceInfoType, + mCorrespondsToInactiveBluetoothRoute); } } - private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { + /** + * Holds route information about an {@link AudioDeviceInfo#getType() audio device info type}. + */ + private static class SystemRouteInfo { + /** The type to use for {@link MediaRoute2Info#getType()}. */ + public final int mMediaRoute2InfoType; + + /** + * Holds the route id to use if no other id is provided. + * + * <p>We only expect this id to be used for non-bluetooth routes. For bluetooth routes, in a + * normal scenario, the id is generated from the device information (like address, or + * hiSyncId), and this value is ignored. A non-normal scenario may occur when there's race + * condition between {@link BluetoothAdapter} and {@link AudioManager}, who are not + * synchronized. + */ + public final String mDefaultRouteId; + + /** + * The name to use for {@link MediaRoute2Info#getName()}. + * + * <p>Usually replaced by the UI layer with a localized string. + */ + public final int mNameResource; + + private SystemRouteInfo(int mediaRoute2InfoType, String defaultRouteId, int nameResource) { + mMediaRoute2InfoType = mediaRoute2InfoType; + mDefaultRouteId = defaultRouteId; + mNameResource = nameResource; + } + } + private class AudioDeviceCallbackImpl extends AudioDeviceCallback { + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) @Override - public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) { - boolean isDeviceRouteChanged; - MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes); - - synchronized (AudioPoliciesDeviceRouteController.this) { - mDeviceRoute = deviceRoute; - isDeviceRouteChanged = mSelectedRoute == null; + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + for (AudioDeviceInfo deviceInfo : addedDevices) { + if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) { + // When a new valid media output is connected, we clear any routing policies so + // that the default routing logic from the audio framework kicks in. As a result + // of this, when the user connects a bluetooth device or a wired headset, the + // new device becomes the active route, which is the traditional behavior. + mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia); + rebuildAvailableRoutes(); + break; + } } + } - if (isDeviceRouteChanged) { - mOnDeviceRouteChangedListener.onDeviceRouteChanged(); + @RequiresPermission( + anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE + }) + @Override + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + for (AudioDeviceInfo deviceInfo : removedDevices) { + if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) { + rebuildAvailableRoutes(); + break; + } } } } + static { + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + new SystemRouteInfo( + MediaRoute2Info.TYPE_BUILTIN_SPEAKER, + /* defaultRouteId= */ "ROUTE_ID_BUILTIN_SPEAKER", + /* nameResource= */ R.string.default_audio_route_name)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_WIRED_HEADSET, + new SystemRouteInfo( + MediaRoute2Info.TYPE_WIRED_HEADSET, + /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADSET", + /* nameResource= */ R.string.default_audio_route_name_headphones)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + new SystemRouteInfo( + MediaRoute2Info.TYPE_WIRED_HEADPHONES, + /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADPHONES", + /* nameResource= */ R.string.default_audio_route_name_headphones)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + new SystemRouteInfo( + MediaRoute2Info.TYPE_BLUETOOTH_A2DP, + /* defaultRouteId= */ "ROUTE_ID_BLUETOOTH_A2DP", + /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_HDMI, + new SystemRouteInfo( + MediaRoute2Info.TYPE_HDMI, + /* defaultRouteId= */ "ROUTE_ID_HDMI", + /* nameResource= */ R.string.default_audio_route_name_external_device)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_DOCK, + new SystemRouteInfo( + MediaRoute2Info.TYPE_DOCK, + /* defaultRouteId= */ "ROUTE_ID_DOCK", + /* nameResource= */ R.string.default_audio_route_name_dock_speakers)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_USB_DEVICE, + new SystemRouteInfo( + MediaRoute2Info.TYPE_USB_DEVICE, + /* defaultRouteId= */ "ROUTE_ID_USB_DEVICE", + /* nameResource= */ R.string.default_audio_route_name_usb)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_USB_HEADSET, + new SystemRouteInfo( + MediaRoute2Info.TYPE_USB_HEADSET, + /* defaultRouteId= */ "ROUTE_ID_USB_HEADSET", + /* nameResource= */ R.string.default_audio_route_name_usb)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_HDMI_ARC, + new SystemRouteInfo( + MediaRoute2Info.TYPE_HDMI_ARC, + /* defaultRouteId= */ "ROUTE_ID_HDMI_ARC", + /* nameResource= */ R.string.default_audio_route_name_external_device)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_HDMI_EARC, + new SystemRouteInfo( + MediaRoute2Info.TYPE_HDMI_EARC, + /* defaultRouteId= */ "ROUTE_ID_HDMI_EARC", + /* nameResource= */ R.string.default_audio_route_name_external_device)); + // TODO: b/305199571 - Add a proper type constants and human readable names for AUX_LINE, + // LINE_ANALOG, LINE_DIGITAL, BLE_BROADCAST, BLE_SPEAKER, BLE_HEADSET, and HEARING_AID. + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_HEARING_AID, + new SystemRouteInfo( + MediaRoute2Info.TYPE_HEARING_AID, + /* defaultRouteId= */ "ROUTE_ID_HEARING_AID", + /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_BLE_HEADSET, + new SystemRouteInfo( + MediaRoute2Info.TYPE_BLE_HEADSET, + /* defaultRouteId= */ "ROUTE_ID_BLE_HEADSET", + /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_BLE_SPEAKER, + new SystemRouteInfo( + MediaRoute2Info.TYPE_BLE_HEADSET, // TODO: b/305199571 - Make a new type. + /* defaultRouteId= */ "ROUTE_ID_BLE_SPEAKER", + /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_BLE_BROADCAST, + new SystemRouteInfo( + MediaRoute2Info.TYPE_BLE_HEADSET, + /* defaultRouteId= */ "ROUTE_ID_BLE_BROADCAST", + /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_LINE_DIGITAL, + new SystemRouteInfo( + MediaRoute2Info.TYPE_UNKNOWN, + /* defaultRouteId= */ "ROUTE_ID_LINE_DIGITAL", + /* nameResource= */ R.string.default_audio_route_name_external_device)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_LINE_ANALOG, + new SystemRouteInfo( + MediaRoute2Info.TYPE_UNKNOWN, + /* defaultRouteId= */ "ROUTE_ID_LINE_ANALOG", + /* nameResource= */ R.string.default_audio_route_name_external_device)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_AUX_LINE, + new SystemRouteInfo( + MediaRoute2Info.TYPE_UNKNOWN, + /* defaultRouteId= */ "ROUTE_ID_AUX_LINE", + /* nameResource= */ R.string.default_audio_route_name_external_device)); + AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( + AudioDeviceInfo.TYPE_DOCK_ANALOG, + new SystemRouteInfo( + MediaRoute2Info.TYPE_DOCK, + /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG", + /* nameResource= */ R.string.default_audio_route_name_dock_speakers)); + } } diff --git a/services/core/java/com/android/server/media/AudioRoutingUtils.java b/services/core/java/com/android/server/media/AudioRoutingUtils.java new file mode 100644 index 000000000000..13f11eb80ece --- /dev/null +++ b/services/core/java/com/android/server/media/AudioRoutingUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.media; + +import android.Manifest; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.audiopolicy.AudioProductStrategy; + +/** Holds utils related to routing in the audio framework. */ +/* package */ final class AudioRoutingUtils { + + /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); + + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + @Nullable + /* package */ static AudioProductStrategy getMediaAudioProductStrategy() { + for (AudioProductStrategy strategy : AudioManager.getAudioProductStrategies()) { + if (strategy.supportsAudioAttributes(AudioRoutingUtils.ATTRIBUTES_MEDIA)) { + return strategy; + } + } + return null; + } + + private AudioRoutingUtils() { + // no-op to prevent instantiation. + } +} diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java index 2b01001fd7d1..74fdf6ee1d7f 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteController.java +++ b/services/core/java/com/android/server/media/BluetoothRouteController.java @@ -44,19 +44,11 @@ import java.util.Objects; @NonNull static BluetoothRouteController createInstance(@NonNull Context context, @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) { - Objects.requireNonNull(context); Objects.requireNonNull(listener); + BluetoothAdapter btAdapter = context.getSystemService(BluetoothManager.class).getAdapter(); - BluetoothManager bluetoothManager = (BluetoothManager) - context.getSystemService(Context.BLUETOOTH_SERVICE); - BluetoothAdapter btAdapter = bluetoothManager.getAdapter(); - - if (btAdapter == null) { + if (btAdapter == null || Flags.enableAudioPoliciesDeviceAndBluetoothController()) { return new NoOpBluetoothRouteController(); - } - - if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { - return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener); } else { return new LegacyBluetoothRouteController(context, btAdapter, listener); } @@ -74,17 +66,6 @@ import java.util.Objects; */ void stop(); - - /** - * Selects the route with the given {@code deviceAddress}. - * - * @param deviceAddress The physical address of the device to select. May be null to unselect - * the currently selected device. - * @return Whether the selection succeeds. If the selection fails, the state of the instance - * remains unaltered. - */ - boolean selectRoute(@Nullable String deviceAddress); - /** * Transfers Bluetooth output to the given route. * @@ -158,12 +139,6 @@ import java.util.Objects; } @Override - public boolean selectRoute(String deviceAddress) { - // no op - return false; - } - - @Override public void transferTo(String routeId) { // no op } diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 0fdaaa7604e5..9f175a9a0277 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -16,17 +16,25 @@ package com.android.server.media; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; import android.content.Context; import android.media.AudioManager; -import android.media.IAudioRoutesObserver; import android.media.IAudioService; import android.media.MediaRoute2Info; +import android.media.audiopolicy.AudioProductStrategy; +import android.os.Looper; import android.os.ServiceManager; +import android.os.UserHandle; import com.android.media.flags.Flags; +import java.util.List; + /** * Controls device routes. * @@ -37,44 +45,65 @@ import com.android.media.flags.Flags; */ /* package */ interface DeviceRouteController { - /** - * Returns a new instance of {@link DeviceRouteController}. - */ - /* package */ static DeviceRouteController createInstance(@NonNull Context context, + /** Returns a new instance of {@link DeviceRouteController}. */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + /* package */ static DeviceRouteController createInstance( + @NonNull Context context, + @NonNull Looper looper, @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { AudioManager audioManager = context.getSystemService(AudioManager.class); - IAudioService audioService = IAudioService.Stub.asInterface( - ServiceManager.getService(Context.AUDIO_SERVICE)); + AudioProductStrategy strategyForMedia = AudioRoutingUtils.getMediaAudioProductStrategy(); - if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { - return new AudioPoliciesDeviceRouteController(context, + BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); + BluetoothAdapter btAdapter = + bluetoothManager != null ? bluetoothManager.getAdapter() : null; + + // TODO: b/305199571 - Make the audio policies implementation work without the need for a + // bluetooth adapter or a strategy for media. If no strategy for media is available we can + // disallow media router transfers, and without a bluetooth adapter we can remove support + // for transfers to inactive bluetooth routes. + if (strategyForMedia != null + && btAdapter != null + && Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + return new AudioPoliciesDeviceRouteController( + context, audioManager, - audioService, + looper, + strategyForMedia, + btAdapter, onDeviceRouteChangedListener); } else { - return new LegacyDeviceRouteController(context, - audioManager, - audioService, - onDeviceRouteChangedListener); + IAudioService audioService = + IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); + return new LegacyDeviceRouteController( + context, audioManager, audioService, onDeviceRouteChangedListener); } } + /** Returns the currently selected device (built-in or wired) route. */ + @NonNull + MediaRoute2Info getSelectedRoute(); + /** - * Select the route with the given built-in or wired {@link MediaRoute2Info.Type}. - * - * <p>If the type is {@code null} then unselects the route and falls back to the default device - * route observed from - * {@link com.android.server.audio.AudioService#startWatchingRoutes(IAudioRoutesObserver)}. + * Returns all available routes. * - * @param type device type. May be {@code null} to unselect currently selected route. - * @return whether the selection succeeds. If the selection fails the state of the controller - * remains intact. + * <p>Note that this method returns available routes including the selected route because (a) + * this interface doesn't guarantee that the internal state of the controller won't change + * between calls to {@link #getSelectedRoute()} and this method and (b) {@link + * #getSelectedRoute()} may be treated as a transferable route (not a selected route) if the + * selected route is from {@link BluetoothRouteController}. */ - boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type); + List<MediaRoute2Info> getAvailableRoutes(); - /** Returns the currently selected device (built-in or wired) route. */ - @NonNull - MediaRoute2Info getSelectedRoute(); + /** + * Transfers device output to the given route. + * + * <p>If the route is {@code null} then active route will be deactivated. + * + * @param routeId to switch to or {@code null} to unset the active device. + */ + void transferTo(@Nullable String routeId); /** * Updates device route volume. @@ -85,6 +114,18 @@ import com.android.media.flags.Flags; boolean updateVolume(int volume); /** + * Starts listening for changes in the system to keep an up to date view of available and + * selected devices. + */ + void start(UserHandle mUser); + + /** + * Stops keeping the internal state up to date with the system, releasing any resources acquired + * in {@link #start} + */ + void stop(); + + /** * Interface for receiving events when device route has changed. */ interface OnDeviceRouteChangedListener { diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index ba3cecf7c091..041fceaf8d3d 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -132,12 +132,6 @@ class LegacyBluetoothRouteController implements BluetoothRouteController { mContext.unregisterReceiver(mDeviceStateChangedReceiver); } - @Override - public boolean selectRoute(String deviceAddress) { - // No-op as the class decides if a route is selected based on Bluetooth events. - return false; - } - /** * Transfers to a given bluetooth route. * The dedicated BT device with the route would be activated. diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java index 65874e23dcdc..c0f28346705c 100644 --- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -35,11 +35,13 @@ import android.media.IAudioRoutesObserver; import android.media.IAudioService; import android.media.MediaRoute2Info; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Slog; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -73,7 +75,6 @@ import java.util.Objects; private int mDeviceVolume; private MediaRoute2Info mDeviceRoute; - @VisibleForTesting /* package */ LegacyDeviceRouteController(@NonNull Context context, @NonNull AudioManager audioManager, @NonNull IAudioService audioService, @@ -100,9 +101,13 @@ import java.util.Objects; } @Override - public boolean selectRoute(@Nullable Integer type) { - // No-op as the controller does not support selection from the outside of the class. - return false; + public void start(UserHandle mUser) { + // Nothing to do. + } + + @Override + public void stop() { + // Nothing to do. } @Override @@ -112,6 +117,17 @@ import java.util.Objects; } @Override + public synchronized List<MediaRoute2Info> getAvailableRoutes() { + return Collections.emptyList(); + } + + @Override + public synchronized void transferTo(@Nullable String routeId) { + // Unsupported. This implementation doesn't support transferable routes (always exposes a + // single non-bluetooth route). + } + + @Override public synchronized boolean updateVolume(int volume) { if (mDeviceVolume == volume) { return false; diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index c8dba800a017..86d78334d546 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -16,15 +16,12 @@ package com.android.server.media; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.media.AudioAttributes; -import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; @@ -51,7 +48,8 @@ import java.util.Set; */ // TODO: check thread safety. We may need to use lock to protect variables. class SystemMediaRoute2Provider extends MediaRoute2Provider { - private static final String TAG = "MR2SystemProvider"; + // Package-visible to use this tag for all system routing logic (done across multiple classes). + /* package */ static final String TAG = "MR2SystemProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final ComponentName COMPONENT_NAME = new ComponentName( @@ -77,26 +75,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { private final AudioManagerBroadcastReceiver mAudioReceiver = new AudioManagerBroadcastReceiver(); - private final AudioManager.OnDevicesForAttributesChangedListener - mOnDevicesForAttributesChangedListener = - new AudioManager.OnDevicesForAttributesChangedListener() { - @Override - public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes, - @NonNull List<AudioDeviceAttributes> devices) { - if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) { - return; - } - - mHandler.post(() -> { - updateSelectedAudioDevice(devices); - notifyProviderState(); - if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); - } - }); - } - }; - private final Object mRequestLock = new Object(); @GuardedBy("mRequestLock") private volatile SessionCreationRequest mPendingSessionCreationRequest; @@ -106,7 +84,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mIsSystemRouteProvider = true; mContext = context; mUser = user; - mHandler = new Handler(Looper.getMainLooper()); + Looper looper = Looper.getMainLooper(); + mHandler = new Handler(looper); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -123,25 +102,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mDeviceRouteController = DeviceRouteController.createInstance( context, - () -> { - mHandler.post( - () -> { - publishProviderState(); - if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); - } - }); - }); - - mAudioManager.addOnDevicesForAttributesChangedListener( - AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(), - mOnDevicesForAttributesChangedListener); - - // These methods below should be called after all fields are initialized, as they - // access the fields inside. - List<AudioDeviceAttributes> devices = - mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA); - updateSelectedAudioDevice(devices); + looper, + () -> + mHandler.post( + () -> { + publishProviderState(); + if (updateSessionInfosIfNeeded()) { + notifySessionInfoUpdated(); + } + })); updateProviderState(); updateSessionInfosIfNeeded(); } @@ -151,20 +120,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); mContext.registerReceiverAsUser(mAudioReceiver, mUser, intentFilter, null, null); - - mHandler.post(() -> { - mBluetoothRouteController.start(mUser); - notifyProviderState(); - }); - updateVolume(); + mHandler.post( + () -> { + mDeviceRouteController.start(mUser); + mBluetoothRouteController.start(mUser); + }); } public void stop() { mContext.unregisterReceiver(mAudioReceiver); - mHandler.post(() -> { - mBluetoothRouteController.stop(); - notifyProviderState(); - }); + mHandler.post( + () -> { + mBluetoothRouteController.stop(); + mDeviceRouteController.stop(); + notifyProviderState(); + }); } @Override @@ -225,13 +195,26 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void transferToRoute(long requestId, String sessionId, String routeId) { if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { // The currently selected route is the default route. + Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT); return; } - MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); - if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) { + boolean isAvailableDeviceRoute = + mDeviceRouteController.getAvailableRoutes().stream() + .anyMatch(it -> it.getId().equals(routeId)); + boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRoute.getId()); + + if (isSelectedDeviceRoute || isAvailableDeviceRoute) { + // The requested route is managed by the device route controller. Note that the selected + // device route doesn't necessarily match mSelectedRouteId (which is the selected route + // of the routing session). If the selected device route is transferred to, we need to + // make the bluetooth routes inactive so that the device route becomes the selected + // route of the routing session. + mDeviceRouteController.transferTo(routeId); mBluetoothRouteController.transferTo(null); } else { + // The requested route is managed by the bluetooth route controller. + mDeviceRouteController.transferTo(null); mBluetoothRouteController.transferTo(routeId); } } @@ -280,33 +263,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); - RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( - SYSTEM_SESSION_ID, packageName).setSystemSession(true); + RoutingSessionInfo.Builder builder = + new RoutingSessionInfo.Builder(SYSTEM_SESSION_ID, packageName) + .setSystemSession(true); builder.addSelectedRoute(selectedDeviceRoute.getId()); for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { builder.addTransferableRoute(route.getId()); } - return builder.setProviderId(mUniqueId).build(); - } - } - private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) { - if (devices.isEmpty()) { - Slog.w(TAG, "The list of preferred devices was empty."); - return; - } - - AudioDeviceAttributes audioDeviceAttributes = devices.get(0); - - if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) { - mDeviceRouteController.selectRoute( - AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes)); - mBluetoothRouteController.selectRoute(null); - } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) { - mDeviceRouteController.selectRoute(null); - mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress()); - } else { - Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes); + if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) { + if (!TextUtils.equals(selectedDeviceRoute.getId(), route.getId())) { + builder.addTransferableRoute(route.getId()); + } + } + } + return builder.setProviderId(mUniqueId).build(); } } @@ -314,7 +286,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); // We must have a device route in the provider info. - builder.addRoute(mDeviceRouteController.getSelectedRoute()); + if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + List<MediaRoute2Info> deviceRoutes = mDeviceRouteController.getAvailableRoutes(); + for (MediaRoute2Info route : deviceRoutes) { + builder.addRoute(route); + } + setProviderState(builder.build()); + } else { + builder.addRoute(mDeviceRouteController.getSelectedRoute()); + } for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { builder.addRoute(route); @@ -352,7 +332,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { .setProviderId(mUniqueId) .build(); builder.addSelectedRoute(mSelectedRouteId); - + for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) { + String routeId = route.getId(); + if (!mSelectedRouteId.equals(routeId)) { + builder.addTransferableRoute(routeId); + } + } for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) { builder.addTransferableRoute(route.getId()); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index aa0b9b892220..3c6887c17e97 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -26,6 +26,7 @@ import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; import static android.app.Notification.FLAG_INSISTENT; +import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; @@ -58,6 +59,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.app.Flags.lifetimeExtensionRefactor; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; @@ -1224,7 +1226,7 @@ public class NotificationManagerService extends SystemService { public void onClearAll(int callingUid, int callingPid, int userId) { synchronized (mNotificationLock) { cancelAllLocked(callingUid, callingPid, userId, REASON_CANCEL_ALL, null, - /*includeCurrentProfiles*/ true); + /*includeCurrentProfiles*/ true, FLAG_ONGOING_EVENT | FLAG_NO_CLEAR); } } @@ -1498,6 +1500,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { + r.recordSmartReplied(); LogMaker logMaker = r.getLogMaker() .setCategory(MetricsEvent.SMART_REPLY_ACTION) .setSubtype(replyIndex) @@ -1804,11 +1807,22 @@ public class NotificationManagerService extends SystemService { record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY)); } if (record != null) { - cancelNotification(record.getSbn().getUid(), record.getSbn().getInitialPid(), - record.getSbn().getPackageName(), record.getSbn().getTag(), - record.getSbn().getId(), 0, - FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB, - true, record.getUserId(), REASON_TIMEOUT, null); + if (lifetimeExtensionRefactor()) { + cancelNotification(record.getSbn().getUid(), + record.getSbn().getInitialPid(), + record.getSbn().getPackageName(), record.getSbn().getTag(), + record.getSbn().getId(), 0, + FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB + | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, + true, record.getUserId(), REASON_TIMEOUT, null); + } else { + cancelNotification(record.getSbn().getUid(), + record.getSbn().getInitialPid(), + record.getSbn().getPackageName(), record.getSbn().getTag(), + record.getSbn().getId(), 0, + FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB, + true, record.getUserId(), REASON_TIMEOUT, null); + } } } } @@ -3728,8 +3742,16 @@ public class NotificationManagerService extends SystemService { @Override public void cancelNotificationWithTag(String pkg, String opPkg, String tag, int id, int userId) { + // Don't allow client applications to cancel foreground service notifs, user-initiated + // job notifs, autobundled summaries, or notifs that have been replied to. + int mustNotHaveFlags = isCallingUidSystem() ? 0 : + (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY); + if (lifetimeExtensionRefactor()) { + mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + } + cancelNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(), - tag, id, userId); + tag, id, userId, mustNotHaveFlags); } @Override @@ -3740,9 +3762,16 @@ public class NotificationManagerService extends SystemService { Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); // Don't allow the app to cancel active FGS or UIJ notifications - cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(), - pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB, - userId, REASON_APP_CANCEL_ALL); + if (lifetimeExtensionRefactor()) { + cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(), + pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB + | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, + userId, REASON_APP_CANCEL_ALL); + } else { + cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(), + pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB, + userId, REASON_APP_CANCEL_ALL); + } } @Override @@ -4808,8 +4837,16 @@ public class NotificationManagerService extends SystemService { r.getSbn().getId(), userId, reason); } } else { - cancelAllLocked(callingUid, callingPid, info.userid, - REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles()); + if (lifetimeExtensionRefactor()) { + cancelAllLocked(callingUid, callingPid, info.userid, + REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(), + FLAG_ONGOING_EVENT | FLAG_NO_CLEAR + | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + } else { + cancelAllLocked(callingUid, callingPid, info.userid, + REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(), + FLAG_ONGOING_EVENT | FLAG_NO_CLEAR); + } } } } finally { @@ -4923,6 +4960,9 @@ public class NotificationManagerService extends SystemService { int callingUid, int callingPid, String pkg, String tag, int id, int userId, int reason) { int mustNotHaveFlags = FLAG_ONGOING_EVENT; + if (lifetimeExtensionRefactor()) { + mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + } cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */, mustNotHaveFlags, true, @@ -6712,7 +6752,12 @@ public class NotificationManagerService extends SystemService { @Override public void cancelNotification(String pkg, String opPkg, int callingUid, int callingPid, String tag, int id, int userId) { - cancelNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, userId); + // Don't allow client applications to cancel foreground service notifs, + // user-initiated job notifs or autobundled summaries. + final int mustNotHaveFlags = isCallingUidSystem() ? 0 : + (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY); + cancelNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, userId, + mustNotHaveFlags); } @Override @@ -6907,7 +6952,7 @@ public class NotificationManagerService extends SystemService { } void cancelNotificationInternal(String pkg, String opPkg, int callingUid, int callingPid, - String tag, int id, int userId) { + String tag, int id, int userId, int mustNotHaveFlags) { userId = ActivityManager.handleIncomingUser(callingPid, callingUid, userId, true, false, "cancelNotificationWithTag", pkg); @@ -6935,10 +6980,6 @@ public class NotificationManagerService extends SystemService { } } - // Don't allow client applications to cancel foreground service notifs, user-initiated job - // notifs or autobundled summaries. - final int mustNotHaveFlags = isCallingUidSystem() ? 0 : - (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY); cancelNotification(uid, callingPid, pkg, tag, id, 0, mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null); } @@ -7294,6 +7335,11 @@ public class NotificationManagerService extends SystemService { notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED; + // Apps should not create notifications that are lifetime extended. + if (lifetimeExtensionRefactor()) { + notification.flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + } + if (notification.fullScreenIntent != null) { final AttributionSource attributionSource = new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); @@ -10088,7 +10134,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") void cancelAllLocked(int callingUid, int callingPid, int userId, int reason, - ManagedServiceInfo listener, boolean includeCurrentProfiles) { + ManagedServiceInfo listener, boolean includeCurrentProfiles, int mustNotHaveFlags) { final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime(); mHandler.post(new Runnable() { @Override @@ -10100,7 +10146,7 @@ public class NotificationManagerService extends SystemService { null, userId, 0, 0, reason, listenerName); FlagChecker flagChecker = (int flags) -> { - int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + int flagsToCheck = mustNotHaveFlags; if (REASON_LISTENER_CANCEL_ALL == reason || REASON_CANCEL_ALL == reason) { flagsToCheck |= FLAG_BUBBLE; diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 100c63863f7f..64d3a20e9281 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -24,7 +24,9 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; +import android.annotation.FlaggedApi; import android.annotation.Nullable; +import android.app.Flags; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationChannel; @@ -1257,10 +1259,27 @@ public final class NotificationRecord { mStats.setExpanded(); } + /** Run when the notification is direct replied. */ public void recordDirectReplied() { + if (Flags.lifetimeExtensionRefactor()) { + // Mark the NotificationRecord as lifetime extended. + Notification notification = getSbn().getNotification(); + notification.flags |= Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + } + mStats.setDirectReplied(); } + + /** Run when the notification is smart replied. */ + @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public void recordSmartReplied() { + Notification notification = getSbn().getNotification(); + notification.flags |= Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + + mStats.setSmartReplied(); + } + public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) { mStats.setDismissalSurface(surface); } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 3b3d79e7dee1..07e0ddfac76b 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -357,6 +357,12 @@ final class DeletePackageHelper { final DeletePackageAction action; synchronized (mPm.mLock) { final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + if (ps == null) { + if (DEBUG_REMOVE) { + Slog.d(TAG, "Attempted to remove non-existent package " + packageName); + } + return false; + } final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps); if (PackageManagerServiceUtils.isSystemApp(ps) && mPm.checkPermission(CONTROL_KEYGUARD, packageName, UserHandle.USER_SYSTEM) diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 0a81b2b9fabb..5494bd9808c8 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -539,6 +539,12 @@ final class InstallRequest { } @Nullable + public PackageSetting getScanRequestDisabledPackageSetting() { + assertScanResultExists(); + return mScanResult.mRequest.mDisabledPkgSetting; + } + + @Nullable public String getRealPackageName() { assertScanResultExists(); return mScanResult.mRequest.mRealPkgName; diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index d0fdfa9bc775..9384c13e583b 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -856,9 +856,9 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable // We may not yet have disabled the updated package yet, so be sure to grab the // current setting if that's the case. final PackageSetting updatedSystemPs = isUpdatedSystemApp - ? installRequest.getDisabledPackageSetting() == null + ? installRequest.getScanRequestDisabledPackageSetting() == null ? installRequest.getScanRequestOldPackageSetting() - : installRequest.getDisabledPackageSetting() + : installRequest.getScanRequestDisabledPackageSetting() : null; if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null || updatedSystemPs.getPkg().getLibraryNames() == null)) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f90bf4b47644..b89b4a2cb241 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1566,6 +1566,7 @@ public class UserManagerService extends IUserManager.Stub { : now - userData.info.creationTime); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.REQUEST_QUIET_MODE_ENABLED) + .setInt(UserJourneyLogger.getUserTypeForStatsd(userData.info.userType)) .setStrings(callingPackage) .setBoolean(enableQuietMode) .setTimePeriod(period) diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 73c422490330..e817df8c6e0c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3638,15 +3638,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { return true; } break; - case KeyEvent.KEYCODE_SPACE: - // Handle keyboard layout switching. (META + SPACE) - if (firstDown && event.isMetaPressed()) { - int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - sendSwitchKeyboardLayout(event, direction); - logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH); - return true; - } - break; case KeyEvent.KEYCODE_META_LEFT: case KeyEvent.KEYCODE_META_RIGHT: if (down) { diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java index e3f36385ef6c..5d9085144334 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java @@ -32,7 +32,6 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.IndentingPrintWriter; -import android.util.KeyValueListParser; import android.util.Slog; import android.view.accessibility.AccessibilityManager; @@ -41,6 +40,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ConcurrentUtils; +import com.android.server.utils.UserSettingDeviceConfigMediator; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -809,12 +809,13 @@ public class BatterySaverPolicy extends ContentObserver implements private static Policy fromSettings(String settings, String deviceSpecificSettings, DeviceConfig.Properties properties, String configSuffix, Policy defaultPolicy) { - final KeyValueListParser parser = new KeyValueListParser(','); + final UserSettingDeviceConfigMediator userSettingDeviceConfigMediator = + new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(','); configSuffix = TextUtils.emptyIfNull(configSuffix); // Device-specific parameters. try { - parser.setString(deviceSpecificSettings == null ? "" : deviceSpecificSettings); + userSettingDeviceConfigMediator.setSettingsString(deviceSpecificSettings); } catch (IllegalArgumentException e) { Slog.wtf(TAG, "Bad device specific battery saver constants: " + deviceSpecificSettings); @@ -822,68 +823,58 @@ public class BatterySaverPolicy extends ContentObserver implements // Non-device-specific parameters. try { - parser.setString(settings == null ? "" : settings); + userSettingDeviceConfigMediator.setSettingsString(settings); + userSettingDeviceConfigMediator.setDeviceConfigProperties(properties); } catch (IllegalArgumentException e) { Slog.wtf(TAG, "Bad battery saver constants: " + settings); } // The Settings value overrides everything, since that will be set by the user. // The DeviceConfig value takes second place, with the default as the last choice. - final float adjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, - properties.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix, - defaultPolicy.adjustBrightnessFactor)); - final boolean advertiseIsEnabled = parser.getBoolean(KEY_ADVERTISE_IS_ENABLED, - properties.getBoolean(KEY_ADVERTISE_IS_ENABLED + configSuffix, - defaultPolicy.advertiseIsEnabled)); - final boolean deferFullBackup = parser.getBoolean(KEY_DEFER_FULL_BACKUP, - properties.getBoolean(KEY_DEFER_FULL_BACKUP + configSuffix, - defaultPolicy.deferFullBackup)); - final boolean deferKeyValueBackup = parser.getBoolean(KEY_DEFER_KEYVALUE_BACKUP, - properties.getBoolean(KEY_DEFER_KEYVALUE_BACKUP + configSuffix, - defaultPolicy.deferKeyValueBackup)); - final boolean disableAnimation = parser.getBoolean(KEY_DISABLE_ANIMATION, - properties.getBoolean(KEY_DISABLE_ANIMATION + configSuffix, - defaultPolicy.disableAnimation)); - final boolean disableAod = parser.getBoolean(KEY_DISABLE_AOD, - properties.getBoolean(KEY_DISABLE_AOD + configSuffix, - defaultPolicy.disableAod)); - final boolean disableLaunchBoost = parser.getBoolean(KEY_DISABLE_LAUNCH_BOOST, - properties.getBoolean(KEY_DISABLE_LAUNCH_BOOST + configSuffix, - defaultPolicy.disableLaunchBoost)); - final boolean disableOptionalSensors = parser.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS, - properties.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS + configSuffix, - defaultPolicy.disableOptionalSensors)); - final boolean disableVibrationConfig = parser.getBoolean(KEY_DISABLE_VIBRATION, - properties.getBoolean(KEY_DISABLE_VIBRATION + configSuffix, - defaultPolicy.disableVibration)); - final boolean enableBrightnessAdjustment = parser.getBoolean( - KEY_ENABLE_BRIGHTNESS_ADJUSTMENT, - properties.getBoolean(KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix, - defaultPolicy.enableAdjustBrightness)); - final boolean enableDataSaver = parser.getBoolean(KEY_ENABLE_DATASAVER, - properties.getBoolean(KEY_ENABLE_DATASAVER + configSuffix, - defaultPolicy.enableDataSaver)); - final boolean enableFirewall = parser.getBoolean(KEY_ENABLE_FIREWALL, - properties.getBoolean(KEY_ENABLE_FIREWALL + configSuffix, - defaultPolicy.enableFirewall)); - final boolean enableNightMode = parser.getBoolean(KEY_ENABLE_NIGHT_MODE, - properties.getBoolean(KEY_ENABLE_NIGHT_MODE + configSuffix, - defaultPolicy.enableNightMode)); - final boolean enableQuickDoze = parser.getBoolean(KEY_ENABLE_QUICK_DOZE, - properties.getBoolean(KEY_ENABLE_QUICK_DOZE + configSuffix, - defaultPolicy.enableQuickDoze)); - final boolean forceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, - properties.getBoolean(KEY_FORCE_ALL_APPS_STANDBY + configSuffix, - defaultPolicy.forceAllAppsStandby)); - final boolean forceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, - properties.getBoolean(KEY_FORCE_BACKGROUND_CHECK + configSuffix, - defaultPolicy.forceBackgroundCheck)); - final int locationMode = parser.getInt(KEY_LOCATION_MODE, - properties.getInt(KEY_LOCATION_MODE + configSuffix, - defaultPolicy.locationMode)); - final int soundTriggerMode = parser.getInt(KEY_SOUNDTRIGGER_MODE, - properties.getInt(KEY_SOUNDTRIGGER_MODE + configSuffix, - defaultPolicy.soundTriggerMode)); + final float adjustBrightnessFactor = userSettingDeviceConfigMediator.getFloat( + KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix, + defaultPolicy.adjustBrightnessFactor); + final boolean advertiseIsEnabled = userSettingDeviceConfigMediator.getBoolean( + KEY_ADVERTISE_IS_ENABLED + configSuffix, + defaultPolicy.advertiseIsEnabled); + final boolean deferFullBackup = userSettingDeviceConfigMediator.getBoolean( + KEY_DEFER_FULL_BACKUP + configSuffix, defaultPolicy.deferFullBackup); + final boolean deferKeyValueBackup = userSettingDeviceConfigMediator.getBoolean( + KEY_DEFER_KEYVALUE_BACKUP + configSuffix, + defaultPolicy.deferKeyValueBackup); + final boolean disableAnimation = userSettingDeviceConfigMediator.getBoolean( + KEY_DISABLE_ANIMATION + configSuffix, defaultPolicy.disableAnimation); + final boolean disableAod = userSettingDeviceConfigMediator.getBoolean( + KEY_DISABLE_AOD + configSuffix, defaultPolicy.disableAod); + final boolean disableLaunchBoost = userSettingDeviceConfigMediator.getBoolean( + KEY_DISABLE_LAUNCH_BOOST + configSuffix, + defaultPolicy.disableLaunchBoost); + final boolean disableOptionalSensors = userSettingDeviceConfigMediator.getBoolean( + KEY_DISABLE_OPTIONAL_SENSORS + configSuffix, + defaultPolicy.disableOptionalSensors); + final boolean disableVibrationConfig = userSettingDeviceConfigMediator.getBoolean( + KEY_DISABLE_VIBRATION + configSuffix, defaultPolicy.disableVibration); + final boolean enableBrightnessAdjustment = userSettingDeviceConfigMediator.getBoolean( + KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix, + defaultPolicy.enableAdjustBrightness); + final boolean enableDataSaver = userSettingDeviceConfigMediator.getBoolean( + KEY_ENABLE_DATASAVER + configSuffix, defaultPolicy.enableDataSaver); + final boolean enableFirewall = userSettingDeviceConfigMediator.getBoolean( + KEY_ENABLE_FIREWALL + configSuffix, defaultPolicy.enableFirewall); + final boolean enableNightMode = userSettingDeviceConfigMediator.getBoolean( + KEY_ENABLE_NIGHT_MODE + configSuffix, defaultPolicy.enableNightMode); + final boolean enableQuickDoze = userSettingDeviceConfigMediator.getBoolean( + KEY_ENABLE_QUICK_DOZE + configSuffix, defaultPolicy.enableQuickDoze); + final boolean forceAllAppsStandby = userSettingDeviceConfigMediator.getBoolean( + KEY_FORCE_ALL_APPS_STANDBY + configSuffix, + defaultPolicy.forceAllAppsStandby); + final boolean forceBackgroundCheck = userSettingDeviceConfigMediator.getBoolean( + KEY_FORCE_BACKGROUND_CHECK + configSuffix, + defaultPolicy.forceBackgroundCheck); + final int locationMode = userSettingDeviceConfigMediator.getInt( + KEY_LOCATION_MODE + configSuffix, defaultPolicy.locationMode); + final int soundTriggerMode = userSettingDeviceConfigMediator.getInt( + KEY_SOUNDTRIGGER_MODE + configSuffix, defaultPolicy.soundTriggerMode); return new Policy( adjustBrightnessFactor, advertiseIsEnabled, diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index dd39fb02573e..c17d6ab4c85b 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -51,6 +51,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; /** An hint service implementation that runs in System Server process. */ @@ -544,7 +545,11 @@ public final class HintManagerService extends SystemService { if (mHalSessionPtr == 0) return; mNativeWrapper.halCloseHintSession(mHalSessionPtr); mHalSessionPtr = 0; - mToken.unlinkToDeath(this, 0); + try { + mToken.unlinkToDeath(this, 0); + } catch (NoSuchElementException ignored) { + Slogf.d(TAG, "Death link does not exist for session with UID " + mUid); + } } synchronized (mLock) { ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid); diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 0656a6a7d3e9..59766ec7a175 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -35,6 +35,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS; +import static android.hardware.SensorPrivacyManager.EXTRA_NOTIFICATION_ID; import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR; import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; @@ -164,6 +165,7 @@ public final class SensorPrivacyService extends SystemService { private final AppOpsManagerInternal mAppOpsManagerInternal; private final TelephonyManager mTelephonyManager; private final PackageManagerInternal mPackageManagerInternal; + private final NotificationManager mNotificationManager; private CameraPrivacyLightController mCameraPrivacyLightController; @@ -188,6 +190,7 @@ public final class SensorPrivacyService extends SystemService { mActivityTaskManager = context.getSystemService(ActivityTaskManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mPackageManagerInternal = getLocalService(PackageManagerInternal.class); + mNotificationManager = mContext.getSystemService(NotificationManager.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(); } @@ -288,9 +291,18 @@ public final class SensorPrivacyService extends SystemService { @Override public void onReceive(Context context, Intent intent) { setToggleSensorPrivacy( - ((UserHandle) intent.getParcelableExtra( - Intent.EXTRA_USER, android.os.UserHandle.class)).getIdentifier(), OTHER, - intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false); + intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class) + .getIdentifier(), + OTHER, + intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), + false + ); + + int notificationId = + intent.getIntExtra(EXTRA_NOTIFICATION_ID, SystemMessage.NOTE_UNKNOWN); + if (notificationId != SystemMessage.NOTE_UNKNOWN) { + mNotificationManager.cancel(notificationId); + } } }, new IntentFilter(ACTION_DISABLE_TOGGLE_SENSOR_PRIVACY), MANAGE_SENSOR_PRIVACY, null, Context.RECEIVER_EXPORTED); @@ -635,8 +647,6 @@ public final class SensorPrivacyService extends SystemService { notificationId = SystemMessage.NOTE_UNBLOCK_CAM_TOGGLE; } - NotificationManager notificationManager = - mContext.getSystemService(NotificationManager.class); NotificationChannel channel = new NotificationChannel( SENSOR_PRIVACY_CHANNEL_ID, getUiContext().getString(R.string.sensor_privacy_notification_channel_label), @@ -646,7 +656,7 @@ public final class SensorPrivacyService extends SystemService { channel.enableVibration(false); channel.setBlockable(false); - notificationManager.createNotificationChannel(channel); + mNotificationManager.createNotificationChannel(channel); Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes); @@ -669,10 +679,11 @@ public final class SensorPrivacyService extends SystemService { new Intent(ACTION_DISABLE_TOGGLE_SENSOR_PRIVACY) .setPackage(mContext.getPackageName()) .putExtra(EXTRA_SENSOR, sensor) + .putExtra(EXTRA_NOTIFICATION_ID, notificationId) .putExtra(Intent.EXTRA_USER, user), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); - notificationManager.notify(notificationId, + mNotificationManager.notify(notificationId, new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID) .setContentTitle(contentTitle) .setContentText(contentText) diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index e6273d331fce..0467d0cd351d 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -38,6 +38,7 @@ import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvRecordingInfo; import android.media.tv.TvTrackInfo; +import android.media.tv.ad.ITvAdManager; import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppClient; import android.media.tv.interactive.ITvInteractiveAppManager; @@ -345,6 +346,7 @@ public class TvInteractiveAppManagerService extends SystemService { Slogf.d(TAG, "onStart"); } publishBinderService(Context.TV_INTERACTIVE_APP_SERVICE, new BinderService()); + publishBinderService(Context.TV_AD_SERVICE, new TvAdBinderService()); } @Override @@ -688,6 +690,12 @@ public class TvInteractiveAppManagerService extends SystemService { } return session; } + private final class TvAdBinderService extends ITvAdManager.Stub { + @Override + public void startAdService(IBinder sessionToken, int userId) { + } + + } private final class BinderService extends ITvInteractiveAppManager.Stub { diff --git a/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java new file mode 100644 index 000000000000..e5423496fb0e --- /dev/null +++ b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 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.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.provider.DeviceConfig; +import android.util.KeyValueListParser; + +/** + * Helper class to mediate the value to use when a constant exists in both a key=value pair Settings + * constant (that can be parsed by {@link KeyValueListParser}) + * and the {@link DeviceConfig} properties. + */ +public abstract class UserSettingDeviceConfigMediator { + private static final String TAG = UserSettingDeviceConfigMediator.class.getSimpleName(); + + @Nullable + protected DeviceConfig.Properties mProperties; + @NonNull + protected final KeyValueListParser mSettingsParser; + + /** + * @param keyValueListDelimiter The delimiter passed into the {@link KeyValueListParser}. + */ + protected UserSettingDeviceConfigMediator(char keyValueListDelimiter) { + mSettingsParser = new KeyValueListParser(keyValueListDelimiter); + } + + /** + * Sets the key=value list string to read from. Setting {@code null} will clear any previously + * set string. + */ + public void setSettingsString(@Nullable String settings) { + mSettingsParser.setString(settings); + } + + /** + * Sets the DeviceConfig Properties to read from. Setting {@code null} will clear any previously + * set properties. + */ + public void setDeviceConfigProperties(@Nullable DeviceConfig.Properties properties) { + mProperties = properties; + } + + /** + * Get the value for key as a boolean. + * + * @param key The key to lookup. + * @param defaultValue The value to return if the key was not found, or not properly defined. + */ + public abstract boolean getBoolean(@NonNull String key, boolean defaultValue); + + /** + * Get the value for key as a float. + * + * @param key The key to lookup. + * @param defaultValue The value to return if the key was not found, or not properly defined. + */ + public abstract float getFloat(@NonNull String key, float defaultValue); + + /** + * Get the value for key as an int. + * + * @param key The key to lookup. + * @param defaultValue The value to return if the key was not found, or not properly defined. + */ + public abstract int getInt(@NonNull String key, int defaultValue); + + /** + * Get the value for key as a long. + * + * @param key The key to lookup. + * @param defaultValue The value to return if the key was not found, or not properly defined. + */ + public abstract long getLong(@NonNull String key, long defaultValue); + + /** + * Get the value for key as a String. + * + * @param key The key to lookup. + * @param defaultValue The value to return if the key was not found, or not properly defined. + */ + public abstract String getString(@NonNull String key, @Nullable String defaultValue); + + /** + * A mediator in which the existence of a single settings key-value pair will override usage + * of DeviceConfig properties. That is, if the Settings constant has any values set, + * then everything in the DeviceConfig namespace will be ignored. + */ + public static class SettingsOverridesAllMediator extends UserSettingDeviceConfigMediator { + public SettingsOverridesAllMediator(char keyValueListDelimiter) { + super(keyValueListDelimiter); + } + + @Override + public boolean getBoolean(@NonNull String key, boolean defaultValue) { + if (mSettingsParser.size() == 0) { + return mProperties == null + ? defaultValue : mProperties.getBoolean(key, defaultValue); + } + return mSettingsParser.getBoolean(key, defaultValue); + } + + @Override + public float getFloat(@NonNull String key, float defaultValue) { + if (mSettingsParser.size() == 0) { + return mProperties == null ? defaultValue : mProperties.getFloat(key, defaultValue); + } + return mSettingsParser.getFloat(key, defaultValue); + } + + @Override + public int getInt(@NonNull String key, int defaultValue) { + if (mSettingsParser.size() == 0) { + return mProperties == null ? defaultValue : mProperties.getInt(key, defaultValue); + } + return mSettingsParser.getInt(key, defaultValue); + } + + @Override + public long getLong(@NonNull String key, long defaultValue) { + if (mSettingsParser.size() == 0) { + return mProperties == null ? defaultValue : mProperties.getLong(key, defaultValue); + } + return mSettingsParser.getLong(key, defaultValue); + } + + @Override + public String getString(@NonNull String key, @Nullable String defaultValue) { + if (mSettingsParser.size() == 0) { + return mProperties == null + ? defaultValue : mProperties.getString(key, defaultValue); + } + return mSettingsParser.getString(key, defaultValue); + } + } + + /** + * A mediator in which only individual keys in the DeviceConfig namespace will be overridden + * by the same key in the Settings constant. If the Settings constant does not have a specific + * key set, then the DeviceConfig value will be used instead. + */ + public static class SettingsOverridesIndividualMediator + extends UserSettingDeviceConfigMediator { + public SettingsOverridesIndividualMediator(char keyValueListDelimiter) { + super(keyValueListDelimiter); + } + + @Override + public boolean getBoolean(@NonNull String key, boolean defaultValue) { + return mSettingsParser.getBoolean(key, + mProperties == null ? defaultValue : mProperties.getBoolean(key, defaultValue)); + } + + @Override + public float getFloat(@NonNull String key, float defaultValue) { + return mSettingsParser.getFloat(key, + mProperties == null ? defaultValue : mProperties.getFloat(key, defaultValue)); + } + + @Override + public int getInt(@NonNull String key, int defaultValue) { + return mSettingsParser.getInt(key, + mProperties == null ? defaultValue : mProperties.getInt(key, defaultValue)); + } + + @Override + public long getLong(@NonNull String key, long defaultValue) { + return mSettingsParser.getLong(key, + mProperties == null ? defaultValue : mProperties.getLong(key, defaultValue)); + } + + @Override + public String getString(@NonNull String key, @Nullable String defaultValue) { + return mSettingsParser.getString(key, + mProperties == null ? defaultValue : mProperties.getString(key, defaultValue)); + } + } +} diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index b12da61d0b3f..e9c40964aee4 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -258,6 +258,11 @@ public class WebViewUpdateService extends SystemService { } @Override // Binder call + public WebViewProviderInfo getDefaultWebViewPackage() { + return WebViewUpdateService.this.mImpl.getDefaultWebViewPackage(); + } + + @Override // Binder call public WebViewProviderInfo[] getAllWebViewPackages() { return WebViewUpdateService.this.mImpl.getWebViewPackages(); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index cfdef1471f83..60dc4ff224bc 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.webkit; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; @@ -29,8 +30,12 @@ import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; +import com.android.server.LocalServices; +import com.android.server.PinnerService; + import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -88,6 +93,8 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE; private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE; + private static final String PIN_GROUP = "webview"; + private final SystemInterface mSystemInterface; private final Context mContext; @@ -339,6 +346,34 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { return newPackage; } + private void pinWebviewIfRequired(ApplicationInfo appInfo) { + PinnerService pinnerService = LocalServices.getService(PinnerService.class); + int webviewPinQuota = pinnerService.getWebviewPinQuota(); + if (webviewPinQuota <= 0) { + return; + } + + pinnerService.unpinGroup(PIN_GROUP); + + ArrayList<String> apksToPin = new ArrayList<>(); + boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true); + for (String sharedLib : appInfo.sharedLibraryFiles) { + apksToPin.add(sharedLib); + } + apksToPin.add(appInfo.sourceDir); + if (!pinSharedFirst) { + // We want to prioritize pinning of the native library that is most likely used by apps + // which in some build flavors live in the main apk and as a shared library for others. + Collections.reverse(apksToPin); + } + for (String apk : apksToPin) { + if (webviewPinQuota <= 0) { + break; + } + int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP); + webviewPinQuota -= bytesPinned; + } + } /** * This is called when we change WebView provider, either when the current provider is * updated or a new provider is chosen / takes precedence. @@ -347,6 +382,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { synchronized (mLock) { mAnyWebViewInstalled = true; if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { + pinWebviewIfRequired(newPackage.applicationInfo); mCurrentWebViewPackage = newPackage; // The relro creations might 'finish' (not start at all) before @@ -385,6 +421,13 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { return providers; } + @Override + public WebViewProviderInfo getDefaultWebViewPackage() { + throw new IllegalStateException( + "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is" + + " disabled."); + } + private static class ProviderAndPackageInfo { public final WebViewProviderInfo provider; public final PackageInfo packageInfo; diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java index 89cb4c802410..29782d9b8b88 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -24,6 +24,7 @@ import android.os.AsyncTask; import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; +import android.util.AndroidRuntimeException; import android.util.Slog; import android.webkit.UserPackage; import android.webkit.WebViewFactory; @@ -374,6 +375,23 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { return providers; } + /** + * Returns the default WebView provider which should be first availableByDefault option in the + * system config. + */ + @Override + public WebViewProviderInfo getDefaultWebViewPackage() { + WebViewProviderInfo[] webviewProviders = getWebViewPackages(); + for (WebViewProviderInfo provider : webviewProviders) { + if (provider.availableByDefault) { + return provider; + } + } + // This should be unreachable because the config parser enforces that there is at least one + // availableByDefault provider. + throw new AndroidRuntimeException("No available by default WebView Provider."); + } + private static class ProviderAndPackageInfo { public final WebViewProviderInfo provider; public final PackageInfo packageInfo; diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java index a9c3dc45842f..1772ef9c7405 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java @@ -40,6 +40,8 @@ interface WebViewUpdateServiceInterface { WebViewProviderInfo[] getValidWebViewPackages(); + WebViewProviderInfo getDefaultWebViewPackage(); + PackageInfo getCurrentWebViewPackage(); boolean isMultiProcessEnabled(); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d6302e08fedb..bbaa6912417c 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1582,6 +1582,7 @@ class ActivityStarter { // An activity has changed order/visibility or the task is occluded by a transient // activity, so this isn't just deliver-to-top && mMovedToTopActivity == null + && !transitionController.hasOrderChanges() && !transitionController.isTransientHide(startedActivityRootTask)) { // We just delivered to top, so there isn't an actual transition here. if (!forceTransientTransition) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 39e900a97021..4625b4fe07ef 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -60,9 +60,9 @@ import android.widget.Toast; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.util.Preconditions; import com.android.server.UiThread; import com.android.server.am.PendingIntentRecord; +import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.util.HashMap; @@ -236,6 +236,7 @@ public class BackgroundActivityStartController { private final @ActivityManager.ProcessState int mCallingUidProcState; private final boolean mIsCallingUidPersistentSystemProcess; private final BackgroundStartPrivileges mBalAllowedByPiSender; + private final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening; private final BackgroundStartPrivileges mBalAllowedByPiCreator; private final String mRealCallingPackage; private final int mRealCallingUid; @@ -267,20 +268,33 @@ public class BackgroundActivityStartController { mIntent = intent; mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid); if (originatingPendingIntent == null) { - // grant creator BAL privileges unless explicitly opted out - mBalAllowedByPiCreator = + // grant BAL privileges unless explicitly opted out + mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator = checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL; + mBalAllowedByPiSender = + checkedOptions.getPendingIntentBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED + ? BackgroundStartPrivileges.NONE + : BackgroundStartPrivileges.ALLOW_BAL; } else { // for PendingIntents we restrict BAL based on target_sdk - mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator( + mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator( callingUid, callingPackage, checkedOptions); + final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening = + checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED + ? BackgroundStartPrivileges.NONE + : BackgroundStartPrivileges.ALLOW_BAL; + mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator() + ? mBalAllowedByPiCreatorWithHardening + : mBalAllowedByPiCreatorWithoutHardening; + mBalAllowedByPiSender = + PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller( + checkedOptions, realCallingUid, mRealCallingPackage); } - mBalAllowedByPiSender = - PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller( - checkedOptions, realCallingUid, mRealCallingPackage); mAppSwitchState = mService.getBalAppSwitchesState(); mCallingUidProcState = mService.mActiveUids.getUidState(callingUid); mIsCallingUidPersistentSystemProcess = @@ -319,10 +333,6 @@ public class BackgroundActivityStartController { return BackgroundStartPrivileges.NONE; case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: // no explicit choice by the app - let us decide what to do - if (!balRequireOptInByPendingIntentCreator()) { - // if feature is disabled allow - return BackgroundStartPrivileges.ALLOW_BAL; - } if (callingPackage != null) { // determine based on the calling/creating package boolean changeEnabled = CompatChanges.isChangeEnabled( @@ -367,11 +377,6 @@ public class BackgroundActivityStartController { return mOriginatingPendingIntent != null && hasRealCaller(); } - private String dump(BalVerdict resultIfPiCreatorAllowsBal) { - Preconditions.checkState(!isPendingIntent()); - return dump(resultIfPiCreatorAllowsBal, null); - } - private boolean callerIsRealCaller() { return mCallingUid == mRealCallingUid; } @@ -396,6 +401,8 @@ public class BackgroundActivityStartController { sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask()); } sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator); + sb.append("; balAllowedByPiCreatorWithHardening: ") + .append(mBalAllowedByPiCreatorWithHardening); sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal); sb.append("; hasRealCaller: ").append(hasRealCaller()); sb.append("; isPendingIntent: ").append(isPendingIntent()); @@ -579,11 +586,12 @@ public class BackgroundActivityStartController { resultForCaller.allows() && resultForRealCaller.blocks()); } + // Handle cases with explicit opt-in if (resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) { if (DEBUG_ACTIVITY_STARTS) { - Slog.d(TAG, "Activity start explicitly allowed by PI creator. " + Slog.d(TAG, "Activity start explicitly allowed by caller. " + state.dump(resultForCaller, resultForRealCaller)); } return statsLog(resultForCaller, state); @@ -592,11 +600,12 @@ public class BackgroundActivityStartController { && checkedOptions.getPendingIntentBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) { if (DEBUG_ACTIVITY_STARTS) { - Slog.d(TAG, "Activity start explicitly allowed by PI sender. " + Slog.d(TAG, "Activity start explicitly allowed by real caller. " + state.dump(resultForCaller, resultForRealCaller)); } return statsLog(resultForRealCaller, state); } + // Handle PendingIntent cases with default behavior next boolean callerCanAllow = resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; @@ -716,17 +725,18 @@ public class BackgroundActivityStartController { // is allowed, or apps like live wallpaper with non app visible window will be allowed. final boolean appSwitchAllowedOrFg = appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY; - final boolean allowCallingUidStartActivity = - ((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) - && callingUidHasAnyVisibleWindow) - || isCallingUidPersistentSystemProcess; - if (allowCallingUidStartActivity) { + if (appSwitchAllowedOrFg && callingUidHasAnyVisibleWindow) { return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, - "callingUidHasAnyVisibleWindow = " - + callingUid - + ", isCallingUidPersistentSystemProcess = " - + isCallingUidPersistentSystemProcess); + /*background*/ false, "callingUid has visible window"); + } + if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) { + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, + /*background*/ false, "callingUid has non-app visible window"); + } + + if (isCallingUidPersistentSystemProcess) { + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ false, "callingUid is persistent system process"); } // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission @@ -806,13 +816,29 @@ public class BackgroundActivityStartController { "realCallingUid has BAL permission."); } - // don't abort if the realCallingUid has a visible window - // TODO(b/171459802): We should check appSwitchAllowed also - if (state.mRealCallingUidHasAnyVisibleWindow) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, - "realCallingUid has visible (non-toast) window."); + // Normal apps with visible app window will be allowed to start activity if app switching + // is allowed, or apps like live wallpaper with non app visible window will be allowed. + final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW + || state.mAppSwitchState == APP_SWITCH_FG_ONLY; + if (Flags.balImproveRealCallerVisibilityCheck()) { + if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) { + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, "realCallingUid has visible window"); + } + if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) { + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, "realCallingUid has non-app visible window"); + } + } else { + // don't abort if the realCallingUid has a visible window + // TODO(b/171459802): We should check appSwitchAllowed also + if (state.mRealCallingUidHasAnyVisibleWindow) { + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, + "realCallingUid has visible (non-toast) window."); + } } + // if the realCallingUid is a persistent system process, abort if the IntentSender // wasn't allowed to start an activity if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts() diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 2fabb0ea686a..7ce9de4e1c24 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -83,6 +83,7 @@ public abstract class Dimmer { /** * Mark all dims as pending completion on the next call to {@link #updateDims} * + * Called before iterating on mHost's children, first step of dimming. * This is intended for us by the host container, to be called at the beginning of * {@link WindowContainer#prepareSurfaces}. After calling this, the container should * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them @@ -100,8 +101,7 @@ public abstract class Dimmer { /** * Call after invoking {@link WindowContainer#prepareSurfaces} on children as - * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates} - * should be set before calling this method. + * described in {@link #resetDimStates}. * * @param t A transaction in which to update the dims. * @return true if any Dims were updated. diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java new file mode 100644 index 000000000000..e91857f1da82 --- /dev/null +++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2023 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; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER; +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; + +import com.android.internal.protolog.common.ProtoLog; + +import java.io.PrintWriter; + +/** + * Contains the information relative to the changes to apply to the dim layer + */ +public class DimmerAnimationHelper { + private static final String TAG = TAG_WITH_CLASS_NAME ? "DimmerAnimationHelper" : TAG_WM; + private static final int DEFAULT_DIM_ANIM_DURATION_MS = 200; + + /** + * Contains the requested changes + */ + static class Change { + private float mAlpha = -1f; + private int mBlurRadius = -1; + private WindowContainer mDimmingContainer = null; + private int mRelativeLayer = -1; + private static final float EPSILON = 0.0001f; + + Change() {} + + Change(Change other) { + mAlpha = other.mAlpha; + mBlurRadius = other.mBlurRadius; + mDimmingContainer = other.mDimmingContainer; + mRelativeLayer = other.mRelativeLayer; + } + + // Same alpha and blur + boolean hasSameVisualProperties(Change other) { + return Math.abs(mAlpha - other.mAlpha) < EPSILON && mBlurRadius == other.mBlurRadius; + } + + boolean hasSameDimmingContainer(Change other) { + return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer; + } + + void inheritPropertiesFromAnimation(AnimationSpec anim) { + mAlpha = anim.mCurrentAlpha; + mBlurRadius = anim.mCurrentBlur; + } + + @Override + public String toString() { + return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container=" + + mDimmingContainer + ", relativePosition=" + mRelativeLayer; + } + } + + private Change mCurrentProperties = new Change(); + private Change mRequestedProperties = new Change(); + private AnimationSpec mAlphaAnimationSpec; + + private final AnimationAdapterFactory mAnimationAdapterFactory; + private AnimationAdapter mLocalAnimationAdapter; + + DimmerAnimationHelper(AnimationAdapterFactory animationFactory) { + mAnimationAdapterFactory = animationFactory; + } + + void setExitParameters() { + setRequestedRelativeParent(mRequestedProperties.mDimmingContainer, -1 /* relativeLayer */); + setRequestedAppearance(0f /* alpha */, 0 /* blur */); + } + + // Sets a requested change without applying it immediately + void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) { + mRequestedProperties.mDimmingContainer = relativeParent; + mRequestedProperties.mRelativeLayer = relativeLayer; + } + + // Sets a requested change without applying it immediately + void setRequestedAppearance(float alpha, int blurRadius) { + mRequestedProperties.mAlpha = alpha; + mRequestedProperties.mBlurRadius = blurRadius; + } + + /** + * Commit the last changes we received. Called after + * {@link Change#setExitParameters()}, + * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or + * {@link Change#setRequestedAppearance(float, int)} + */ + void applyChanges(SurfaceControl.Transaction t, SmoothDimmer.DimState dim) { + if (mRequestedProperties.mDimmingContainer == null) { + Log.e(TAG, this + " does not have a dimming container. Have you forgotten to " + + "call adjustRelativeLayer?"); + return; + } + if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) { + Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer + + "does not have a surface"); + dim.remove(t); + return; + } + + dim.ensureVisible(t); + relativeReparent(dim.mDimSurface, + mRequestedProperties.mDimmingContainer.getSurfaceControl(), + mRequestedProperties.mRelativeLayer, t); + + if (!mCurrentProperties.hasSameVisualProperties(mRequestedProperties)) { + stopCurrentAnimation(dim.mDimSurface); + + if (dim.mSkipAnimation + // If the container doesn't change but requests a dim change, then it is + // directly providing us the animated values + || (mRequestedProperties.hasSameDimmingContainer(mCurrentProperties) + && dim.isDimming())) { + ProtoLog.d(WM_DEBUG_DIMMER, + "%s skipping animation and directly setting alpha=%f, blur=%d", + dim, mRequestedProperties.mAlpha, + mRequestedProperties.mBlurRadius); + setAlphaBlur(dim.mDimSurface, mRequestedProperties.mAlpha, + mRequestedProperties.mBlurRadius, t); + dim.mSkipAnimation = false; + } else { + startAnimation(t, dim); + } + + } else if (!dim.isDimming()) { + // We are not dimming, so we tried the exit animation but the alpha is already 0, + // therefore, let's just remove this surface + dim.remove(t); + } + mCurrentProperties = new Change(mRequestedProperties); + } + + private void startAnimation( + SurfaceControl.Transaction t, SmoothDimmer.DimState dim) { + ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim); + mAlphaAnimationSpec = getRequestedAnimationSpec(); + mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec, + dim.mHostContainer.mWmService.mSurfaceAnimationRunner); + + float targetAlpha = mRequestedProperties.mAlpha; + int targetBlur = mRequestedProperties.mBlurRadius; + + mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t, + ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> { + setAlphaBlur(dim.mDimSurface, targetAlpha, targetBlur, t); + if (targetAlpha == 0f && !dim.isDimming()) { + dim.remove(t); + } + mLocalAnimationAdapter = null; + mAlphaAnimationSpec = null; + }); + } + + private boolean isAnimating() { + return mAlphaAnimationSpec != null; + } + + void stopCurrentAnimation(SurfaceControl surface) { + if (mLocalAnimationAdapter != null && isAnimating()) { + // Save the current animation progress and cancel the animation + mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec); + mLocalAnimationAdapter.onAnimationCancelled(surface); + mLocalAnimationAdapter = null; + mAlphaAnimationSpec = null; + } + } + + private AnimationSpec getRequestedAnimationSpec() { + final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f); + final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0); + long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer) + * Math.abs(mRequestedProperties.mAlpha - startAlpha)); + + final AnimationSpec spec = new AnimationSpec( + new AnimationSpec.AnimationExtremes<>(startAlpha, mRequestedProperties.mAlpha), + new AnimationSpec.AnimationExtremes<>(startBlur, mRequestedProperties.mBlurRadius), + duration + ); + ProtoLog.v(WM_DEBUG_DIMMER, "Dim animation requested: %s", spec); + return spec; + } + + /** + * Change the relative parent of this dim layer + */ + void relativeReparent(SurfaceControl dimLayer, SurfaceControl relativeParent, + int relativePosition, SurfaceControl.Transaction t) { + try { + t.setRelativeLayer(dimLayer, relativeParent, relativePosition); + } catch (NullPointerException e) { + Log.w(TAG, "Tried to change parent of dim " + dimLayer + " after remove", e); + } + } + + void setAlphaBlur(SurfaceControl sc, float alpha, int blur, SurfaceControl.Transaction t) { + try { + t.setAlpha(sc, alpha); + t.setBackgroundBlurRadius(sc, blur); + } catch (NullPointerException e) { + Log.w(TAG , "Tried to change look of dim " + sc + " after remove", e); + } + } + + private long getDimDuration(WindowContainer container) { + // Use the same duration as the animation on the WindowContainer + AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); + final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); + return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION_MS * durationScale) + : animationAdapter.getDurationHint(); + } + + /** + * Collects the animation specifics + */ + static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec { + private static final String TAG = TAG_WITH_CLASS_NAME ? "DimmerAnimationSpec" : TAG_WM; + + static class AnimationExtremes<T> { + final T mStartValue; + final T mFinishValue; + + AnimationExtremes(T fromValue, T toValue) { + mStartValue = fromValue; + mFinishValue = toValue; + } + + @Override + public String toString() { + return "[" + mStartValue + "->" + mFinishValue + "]"; + } + } + + private final long mDuration; + private final AnimationSpec.AnimationExtremes<Float> mAlpha; + private final AnimationSpec.AnimationExtremes<Integer> mBlur; + + float mCurrentAlpha = 0; + int mCurrentBlur = 0; + boolean mStarted = false; + + AnimationSpec(AnimationSpec.AnimationExtremes<Float> alpha, + AnimationSpec.AnimationExtremes<Integer> blur, long duration) { + mAlpha = alpha; + mBlur = blur; + mDuration = duration; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { + if (!mStarted) { + // The first frame would end up in the sync transaction, and since this could be + // applied after the animation transaction, we avoid putting visible changes here. + // The initial state of the animation matches the current state of the dim anyway. + mStarted = true; + return; + } + final float fraction = getFraction(currentPlayTime); + mCurrentAlpha = + fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue; + mCurrentBlur = + (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue; + if (sc.isValid()) { + t.setAlpha(sc, mCurrentAlpha); + t.setBackgroundBlurRadius(sc, mCurrentBlur); + } else { + Log.w(TAG, "Dimmer#AnimationSpec tried to access " + sc + " after release"); + } + } + + @Override + public String toString() { + return "Animation spec: alpha=" + mAlpha + ", blur=" + mBlur; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue); + pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue); + pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue); + pw.print(" to_blur="); pw.print(mBlur.mFinishValue); + pw.print(" duration="); pw.println(mDuration); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ALPHA); + proto.write(FROM, mAlpha.mStartValue); + proto.write(TO, mAlpha.mFinishValue); + proto.write(DURATION_MS, mDuration); + proto.end(token); + } + } + + static class AnimationAdapterFactory { + public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, + SurfaceAnimationRunner runner) { + return new LocalAnimationAdapter(alphaAnimationSpec, runner); + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index bb599363bdb5..50376fed2005 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1648,7 +1648,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ? new Transition.ReadyCondition("displayConfig", this) : null; if (displayConfig != null) { mTransitionController.waitFor(displayConfig); - } else if (mTransitionController.isShellTransitionsEnabled()) { + } else if (mTransitionController.isShellTransitionsEnabled() && mLastHasContent) { Slog.e(TAG, "Display reconfigured outside of a transition: " + this); } final boolean configUpdated = updateDisplayOverrideConfigurationLocked(); diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java index 2549bbf70e9f..b5d94a2efbdf 100644 --- a/services/core/java/com/android/server/wm/SmoothDimmer.java +++ b/services/core/java/com/android/server/wm/SmoothDimmer.java @@ -17,397 +17,212 @@ package com.android.server.wm; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER; -import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; -import static com.android.server.wm.AlphaAnimationSpecProto.FROM; -import static com.android.server.wm.AlphaAnimationSpecProto.TO; -import static com.android.server.wm.AnimationSpecProto.ALPHA; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.graphics.Rect; import android.util.Log; -import android.util.proto.ProtoOutputStream; import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; -import java.io.PrintWriter; - class SmoothDimmer extends Dimmer { + private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; - private static final float EPSILON = 0.0001f; - // This is in milliseconds. - private static final int DEFAULT_DIM_ANIM_DURATION = 200; DimState mDimState; - private WindowContainer mLastRequestedDimContainer; - private final AnimationAdapterFactory mAnimationAdapterFactory; + final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory; + /** + * Controls the dim behaviour + */ @VisibleForTesting class DimState { - /** - * The layer where property changes should be invoked on. - */ - SurfaceControl mDimLayer; - boolean mDimming; - boolean mIsVisible; - + /** Related objects */ + SurfaceControl mDimSurface; + final WindowContainer mHostContainer; + // The last container to request to dim + private WindowContainer mLastRequestedDimContainer; + /** Animation */ + private final DimmerAnimationHelper mAnimationHelper; + boolean mSkipAnimation = false; + // Determines whether the dim layer should animate before destroying. + boolean mAnimateExit = true; + /** Surface visibility and bounds */ + private boolean mIsVisible = false; // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. final Rect mDimBounds = new Rect(); - /** - * Determines whether the dim layer should animate before destroying. - */ - boolean mAnimateExit = true; - - /** - * Used for Dims not associated with a WindowContainer. - * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim - * lifecycle. - */ - boolean mDontReset; - - Change mCurrentProperties; - Change mRequestedProperties; - private AnimationSpec mAlphaAnimationSpec; - private AnimationAdapter mLocalAnimationAdapter; - - static class Change { - private float mAlpha = -1f; - private int mBlurRadius = -1; - private WindowContainer mDimmingContainer = null; - private int mRelativeLayer = -1; - private boolean mSkipAnimation = false; - - Change() {} - - Change(Change other) { - mAlpha = other.mAlpha; - mBlurRadius = other.mBlurRadius; - mDimmingContainer = other.mDimmingContainer; - mRelativeLayer = other.mRelativeLayer; - } - - @Override - public String toString() { - return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container=" - + mDimmingContainer + ", relativePosition=" + mRelativeLayer - + ", skipAnimation=" + mSkipAnimation; + DimState() { + mHostContainer = mHost; + mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory); + try { + mDimSurface = makeDimLayer(); + } catch (Surface.OutOfResourcesException e) { + Log.w(TAG, "OutOfResourcesException creating dim surface"); } } - DimState(SurfaceControl dimLayer) { - mDimLayer = dimLayer; - mDimming = true; - mCurrentProperties = new Change(); - mRequestedProperties = new Change(); - } - - void setExitParameters(WindowContainer container) { - setRequestedRelativeParent(container, -1 /* relativeLayer */); - setRequestedAppearance(0f /* alpha */, 0 /* blur */); + void ensureVisible(SurfaceControl.Transaction t) { + if (!mIsVisible) { + t.show(mDimSurface); + t.setAlpha(mDimSurface, 0f); + mIsVisible = true; + } } - // Sets a requested change without applying it immediately - void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) { - mRequestedProperties.mDimmingContainer = relativeParent; - mRequestedProperties.mRelativeLayer = relativeLayer; + void adjustSurfaceLayout(SurfaceControl.Transaction t) { + // TODO: Once we use geometry from hierarchy this falls away. + t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top); + t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height()); } - // Sets a requested change without applying it immediately - void setRequestedAppearance(float alpha, int blurRadius) { - mRequestedProperties.mAlpha = alpha; - mRequestedProperties.mBlurRadius = blurRadius; + /** + * Set the parameters to prepare the dim to change its appearance + */ + void prepareLookChange(float alpha, int blurRadius) { + mAnimationHelper.setRequestedAppearance(alpha, blurRadius); } /** - * Commit the last changes we received. Called after - * {@link Change#setExitParameters(WindowContainer)}, - * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or - * {@link Change#setRequestedAppearance(float, int)} + * Prepare the dim for the exit animation */ - void applyChanges(SurfaceControl.Transaction t) { - if (mRequestedProperties.mDimmingContainer == null) { - Log.e(TAG, this + " does not have a dimming container. Have you forgotten to " - + "call adjustRelativeLayer?"); - return; - } - if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) { - Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer - + "does not have a surface"); - return; - } - if (!mDimState.mIsVisible) { - mDimState.mIsVisible = true; - t.show(mDimState.mDimLayer); + void exit(SurfaceControl.Transaction t) { + if (!mAnimateExit) { + remove(t); + } else { + mAnimationHelper.setExitParameters(); + setReady(t); } - t.setRelativeLayer(mDimLayer, - mRequestedProperties.mDimmingContainer.getSurfaceControl(), - mRequestedProperties.mRelativeLayer); + } - if (aspectChanged()) { - if (isAnimating()) { - mLocalAnimationAdapter.onAnimationCancelled(mDimLayer); - } - if (mRequestedProperties.mSkipAnimation - || (!dimmingContainerChanged() && mDimming)) { - // If the dimming container has not changed, then it is running its own - // animation, thus we can directly set the values we get requested, unless it's - // the exiting animation - ProtoLog.d(WM_DEBUG_DIMMER, - "Dim %s skipping animation and directly setting alpha=%f, blur=%d", - mDimLayer, mRequestedProperties.mAlpha, - mRequestedProperties.mBlurRadius); - t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); - t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); - mRequestedProperties.mSkipAnimation = false; - } else { - startAnimation(t); - } + void remove(SurfaceControl.Transaction t) { + mAnimationHelper.stopCurrentAnimation(mDimSurface); + if (mDimSurface.isValid()) { + t.remove(mDimSurface); + ProtoLog.d(WM_DEBUG_DIMMER, + "Removing dim surface %s on transaction %s", this, t); + } else { + Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n"); } - mCurrentProperties = new Change(mRequestedProperties); } - private void startAnimation(SurfaceControl.Transaction t) { - mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha, - mRequestedProperties.mBlurRadius); - mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec, - mHost.mWmService.mSurfaceAnimationRunner); - - mLocalAnimationAdapter.startAnimation(mDimLayer, t, - ANIMATION_TYPE_DIMMER, (type, animator) -> { - t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); - t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); - if (mRequestedProperties.mAlpha == 0f && !mDimming) { - ProtoLog.d(WM_DEBUG_DIMMER, - "Removing dim surface %s on transaction %s", mDimLayer, t); - t.remove(mDimLayer); - } - mLocalAnimationAdapter = null; - mAlphaAnimationSpec = null; - }); + @Override + public String toString() { + return "SmoothDimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface; } - private boolean isAnimating() { - return mAlphaAnimationSpec != null; + /** + * Set the parameters to prepare the dim to be relative parented to the dimming container + */ + void prepareReparent(WindowContainer relativeParent, int relativeLayer) { + mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer); } - private boolean aspectChanged() { - return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON - || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius; + /** + * Call when all the changes have been requested to have them applied + * @param t The transaction in which to apply the changes + */ + void setReady(SurfaceControl.Transaction t) { + mAnimationHelper.applyChanges(t, this); } - private boolean dimmingContainerChanged() { - return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer; + /** + * Whether anyone is currently requesting the dim + */ + boolean isDimming() { + return mLastRequestedDimContainer != null; } - private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) { - final float startAlpha; - final int startBlur; - if (mAlphaAnimationSpec != null) { - startAlpha = mAlphaAnimationSpec.mCurrentAlpha; - startBlur = mAlphaAnimationSpec.mCurrentBlur; - } else { - startAlpha = Math.max(mCurrentProperties.mAlpha, 0f); - startBlur = Math.max(mCurrentProperties.mBlurRadius, 0); - } - long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer) - * Math.abs(targetAlpha - startAlpha)); - - ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, " - + "alpha: %f -> %f, blur: %d -> %d", - mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha, - startBlur, targetBlur); - return new AnimationSpec( - new AnimationExtremes<>(startAlpha, targetAlpha), - new AnimationExtremes<>(startBlur, targetBlur), - duration - ); + private SurfaceControl makeDimLayer() { + return mHost.makeChildSurface(null) + .setParent(mHost.getSurfaceControl()) + .setColorLayer() + .setName("Dim Layer for - " + mHost.getName()) + .setCallsite("DimLayer.makeDimLayer") + .build(); } } protected SmoothDimmer(WindowContainer host) { - this(host, new AnimationAdapterFactory()); + this(host, new DimmerAnimationHelper.AnimationAdapterFactory()); } @VisibleForTesting - SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) { + SmoothDimmer(WindowContainer host, + DimmerAnimationHelper.AnimationAdapterFactory animationFactory) { super(host); mAnimationAdapterFactory = animationFactory; } - private DimState obtainDimState(WindowContainer container) { - if (mDimState == null) { - try { - final SurfaceControl ctl = makeDimLayer(); - mDimState = new DimState(ctl); - } catch (Surface.OutOfResourcesException e) { - Log.w(TAG, "OutOfResourcesException creating dim surface"); - } - } - - mLastRequestedDimContainer = container; - return mDimState; - } - - private SurfaceControl makeDimLayer() { - return mHost.makeChildSurface(null) - .setParent(mHost.getSurfaceControl()) - .setColorLayer() - .setName("Dim Layer for - " + mHost.getName()) - .setCallsite("Dimmer.makeDimLayer") - .build(); - } - - @Override - SurfaceControl getDimLayer() { - return mDimState != null ? mDimState.mDimLayer : null; - } - @Override void resetDimStates() { - if (mDimState == null) { - return; - } - if (!mDimState.mDontReset) { - mDimState.mDimming = false; - } - } - - @Override - Rect getDimBounds() { - return mDimState != null ? mDimState.mDimBounds : null; - } - - @Override - void dontAnimateExit() { if (mDimState != null) { - mDimState.mAnimateExit = false; + mDimState.mLastRequestedDimContainer = null; } } @Override protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) { final DimState d = obtainDimState(container); - mDimState.setRequestedAppearance(alpha, blurRadius); - d.mDimming = true; + d.prepareLookChange(alpha, blurRadius); } @Override protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) { if (mDimState != null) { - mDimState.setRequestedRelativeParent(container, relativeLayer); + mDimState.prepareReparent(container, relativeLayer); } } + @Override boolean updateDims(SurfaceControl.Transaction t) { if (mDimState == null) { return false; } - - if (!mDimState.mDimming) { - // No one is dimming anymore, fade out dim and remove - if (!mDimState.mAnimateExit) { - if (mDimState.mDimLayer.isValid()) { - t.remove(mDimState.mDimLayer); - } - } else { - mDimState.setExitParameters( - mDimState.mRequestedProperties.mDimmingContainer); - mDimState.applyChanges(t); - } + if (!mDimState.isDimming()) { + // No one is dimming, fade out and remove the dim + mDimState.exit(t); mDimState = null; return false; + } else { + // Someone is dimming, show the requested changes + mDimState.adjustSurfaceLayout(t); + final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState(); + if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null + && ws.mActivityRecord.mStartingData != null) { + // Skip enter animation while starting window is on top of its activity + mDimState.mSkipAnimation = true; + } + mDimState.setReady(t); + return true; } - final Rect bounds = mDimState.mDimBounds; - // TODO: Once we use geometry from hierarchy this falls away. - t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); - t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); - // Skip enter animation while starting window is on top of its activity - final WindowState ws = mLastRequestedDimContainer.asWindowState(); - if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null - && ws.mActivityRecord.mStartingData != null) { - mDimState.mRequestedProperties.mSkipAnimation = true; - } - mDimState.applyChanges(t); - return true; - } - - private long getDimDuration(WindowContainer container) { - // Use the same duration as the animation on the WindowContainer - AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); - final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); - return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) - : animationAdapter.getDurationHint(); } - private static class AnimationExtremes<T> { - final T mStartValue; - final T mFinishValue; - - AnimationExtremes(T fromValue, T toValue) { - mStartValue = fromValue; - mFinishValue = toValue; + private DimState obtainDimState(WindowContainer container) { + if (mDimState == null) { + mDimState = new DimState(); } + mDimState.mLastRequestedDimContainer = container; + return mDimState; } - private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec { - private final long mDuration; - private final AnimationExtremes<Float> mAlpha; - private final AnimationExtremes<Integer> mBlur; - - float mCurrentAlpha = 0; - int mCurrentBlur = 0; - - AnimationSpec(AnimationExtremes<Float> alpha, - AnimationExtremes<Integer> blur, long duration) { - mAlpha = alpha; - mBlur = blur; - mDuration = duration; - } - - @Override - public long getDuration() { - return mDuration; - } - - @Override - public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { - final float fraction = getFraction(currentPlayTime); - mCurrentAlpha = - fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue; - mCurrentBlur = - (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue; - t.setAlpha(sc, mCurrentAlpha); - t.setBackgroundBlurRadius(sc, mCurrentBlur); - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue); - pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue); - pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue); - pw.print(" to_blur="); pw.print(mBlur.mFinishValue); - pw.print(" duration="); pw.println(mDuration); - } - - @Override - public void dumpDebugInner(ProtoOutputStream proto) { - final long token = proto.start(ALPHA); - proto.write(FROM, mAlpha.mStartValue); - proto.write(TO, mAlpha.mFinishValue); - proto.write(DURATION_MS, mDuration); - proto.end(token); - } + @Override + @VisibleForTesting + SurfaceControl getDimLayer() { + return mDimState != null ? mDimState.mDimSurface : null; } - static class AnimationAdapterFactory { + @Override + Rect getDimBounds() { + return mDimState != null ? mDimState.mDimBounds : null; + } - public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, - SurfaceAnimationRunner runner) { - return new LocalAnimationAdapter(alphaAnimationSpec, runner); + @Override + void dontAnimateExit() { + if (mDimState != null) { + mDimState.mAnimateExit = false; } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 73755121daf8..5f082124dbcb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -422,6 +422,11 @@ class Task extends TaskFragment { // TODO: remove this once the recents animation is moved to the Shell SurfaceControl mLastRecentsAnimationOverlay; + // A surface that is used by TaskFragmentOrganizer to place content on top of own activities and + // trusted TaskFragments. + @Nullable + DecorSurfaceContainer mDecorSurfaceContainer; + static final int LAYER_RANK_INVISIBLE = -1; // Ranking (from top) of this task among all visible tasks. (-1 means it's not visible) // This number will be assigned when we evaluate OOM scores for all visible tasks. @@ -1540,6 +1545,11 @@ class Task extends TaskFragment { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } + if (mDecorSurfaceContainer != null && r == mDecorSurfaceContainer.mOwnerTaskFragment) { + // Remove the decor surface if the owner TaskFragment is removed; + removeDecorSurface(); + } + if (hasChild()) { updateEffectiveIntent(); @@ -2638,6 +2648,9 @@ class Task extends TaskFragment { } // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); + if (mDecorSurfaceContainer != null) { + mDecorSurfaceContainer.release(); + } super.removeImmediately(); mRemoving = false; @@ -3644,7 +3657,8 @@ class Task extends TaskFragment { */ TaskFragmentParentInfo getTaskFragmentParentInfo() { return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), - shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity()); + shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity(), + getDecorSurface()); } @Override @@ -3666,6 +3680,62 @@ class Task extends TaskFragment { } } + @Override + void assignChildLayers(@NonNull SurfaceControl.Transaction t) { + int layer = 0; + boolean decorSurfacePlaced = false; + + // We use two passes as a way to promote children which + // need Z-boosting to the end of the list. + for (int j = 0; j < mChildren.size(); ++j) { + final WindowContainer wc = mChildren.get(j); + wc.assignChildLayers(t); + if (!wc.needsZBoost()) { + // Place the decor surface under any untrusted content. + if (mDecorSurfaceContainer != null && !decorSurfacePlaced + && shouldPlaceDecorSurfaceBelowContainer(wc)) { + mDecorSurfaceContainer.assignLayer(t, layer++); + decorSurfacePlaced = true; + } + wc.assignLayer(t, layer++); + + // Place the decor surface just above the owner TaskFragment. + if (mDecorSurfaceContainer != null && !decorSurfacePlaced + && wc == mDecorSurfaceContainer.mOwnerTaskFragment) { + mDecorSurfaceContainer.assignLayer(t, layer++); + decorSurfacePlaced = true; + } + } + } + + // If not placed yet, the decor surface should be on top of all non-boosted children. + if (mDecorSurfaceContainer != null && !decorSurfacePlaced) { + mDecorSurfaceContainer.assignLayer(t, layer++); + decorSurfacePlaced = true; + } + + for (int j = 0; j < mChildren.size(); ++j) { + final WindowContainer wc = mChildren.get(j); + if (wc.needsZBoost()) { + wc.assignLayer(t, layer++); + } + } + if (mOverlayHost != null) { + mOverlayHost.setLayer(t, layer++); + } + } + + boolean shouldPlaceDecorSurfaceBelowContainer(@NonNull WindowContainer wc) { + boolean isOwnActivity = + wc.asActivityRecord() != null + && wc.asActivityRecord().isUid(effectiveUid); + boolean isTrustedTaskFragment = + wc.asTaskFragment() != null + && wc.asTaskFragment().isEmbedded() + && wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode(); + return !isOwnActivity && !isTrustedTaskFragment; + } + boolean isTaskId(int taskId) { return mTaskId == taskId; } @@ -6177,6 +6247,7 @@ class Task extends TaskFragment { // Avoid resuming activities on secondary displays since we don't want bubble // activities to be resumed while bubble is still collapsed. // TODO(b/113840485): Having keyguard going away state for secondary displays. + && display != null && display.isDefaultDisplay) { return false; } @@ -6672,4 +6743,77 @@ class Task extends TaskFragment { mOverlayHost.dispatchInsetsChanged(s, mTmpRect); } } + + /** + * Associates the decor surface with the given TF, or create one if there + * isn't one in the Task yet. The surface will be removed with the TF, + * and become invisible if the TF is invisible. */ + void moveOrCreateDecorSurfaceFor(TaskFragment taskFragment) { + if (mDecorSurfaceContainer != null) { + mDecorSurfaceContainer.mOwnerTaskFragment = taskFragment; + } else { + mDecorSurfaceContainer = new DecorSurfaceContainer(taskFragment); + assignChildLayers(); + sendTaskFragmentParentInfoChangedIfNeeded(); + } + } + + void removeDecorSurface() { + if (mDecorSurfaceContainer == null) { + return; + } + mDecorSurfaceContainer.release(); + mDecorSurfaceContainer = null; + sendTaskFragmentParentInfoChangedIfNeeded(); + } + + @Nullable SurfaceControl getDecorSurface() { + return mDecorSurfaceContainer != null ? mDecorSurfaceContainer.mDecorSurface : null; + } + + /** + * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed + * below children windows except for own Activities and TaskFragment in fully trusted mode. + */ + @VisibleForTesting + class DecorSurfaceContainer { + @VisibleForTesting + @NonNull final SurfaceControl mContainerSurface; + + @VisibleForTesting + @NonNull final SurfaceControl mDecorSurface; + + // The TaskFragment that requested the decor surface. If it is destroyed, the decor surface + // is also released. + @VisibleForTesting + @NonNull TaskFragment mOwnerTaskFragment; + + private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) { + mOwnerTaskFragment = initialOwner; + mContainerSurface = makeSurface().setContainerLayer() + .setParent(mSurfaceControl) + .setName(mSurfaceControl + " - decor surface container") + .setEffectLayer() + .setHidden(false) + .setCallsite("Task.DecorSurfaceContainer") + .build(); + + mDecorSurface = makeSurface() + .setParent(mContainerSurface) + .setName(mSurfaceControl + " - decor surface") + .setHidden(false) + .setCallsite("Task.DecorSurfaceContainer") + .build(); + } + + private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) { + t.setLayer(mContainerSurface, layer); + t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible()); + } + + private void release() { + mDecorSurface.release(); + mContainerSurface.release(); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 8bc461f05387..39b4480a7da0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -316,7 +316,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Organizer that organizing this TaskFragment. */ @Nullable private ITaskFragmentOrganizer mTaskFragmentOrganizer; - private int mTaskFragmentOrganizerUid = INVALID_UID; + @VisibleForTesting + int mTaskFragmentOrganizerUid = INVALID_UID; private @Nullable String mTaskFragmentOrganizerProcessName; /** Client assigned unique token for this TaskFragment if this is created by an organizer. */ diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index e7a1cf106a44..707f9fc9ea5f 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -762,6 +762,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setTask(task) .build()); } + // Make sure the parent info changed event will be dispatched if there are no other changes. + mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal(); } boolean isSystemOrganizer(@NonNull IBinder organizerToken) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index caa57bb032ca..e5604eca3df0 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1737,8 +1737,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Since we created root-leash but no longer reference it from core, release it now info.releaseAnimSurfaces(); - mLogger.logOnSendAsync(mController.mLoggerHandler); if (mLogger.mInfo != null) { + mLogger.logOnSendAsync(mController.mLoggerHandler); mController.mTransitionTracer.logSentTransition(this, mTargets); } } @@ -1756,6 +1756,27 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** + * Checks if the transition contains order changes. + * + * This is a shallow check that doesn't account for collection in parallel, unlike + * {@code collectOrderChanges} + */ + boolean hasOrderChanges() { + ArrayList<Task> onTopTasks = new ArrayList<>(); + // Iterate over target displays to get up to date on top tasks. + // Cannot use `mOnTopTasksAtReady` as it's not populated before the `applyReady` is called. + for (DisplayContent dc : mTargetDisplays) { + addOnTopTasks(dc, onTopTasks); + } + for (Task task : onTopTasks) { + if (!mOnTopTasksStart.contains(task)) { + return true; + } + } + return false; + } + + /** * Collect tasks which moved-to-top as part of this transition. This also updates the * controller's latest-reported when relevant. * @@ -3305,7 +3326,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ void addGroup(WindowContainer wc) { if (mReadyGroups.containsKey(wc)) { - Slog.e(TAG, "Trying to add a ready-group twice: " + wc); return; } mReadyGroups.put(wc, false); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index bacfda5fc528..e648d6417e88 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -792,6 +792,12 @@ class TransitionController { mCollectingTransition.recordTaskOrder(wc); } + /** @see Transition#hasOrderChanges */ + boolean hasOrderChanges() { + if (mCollectingTransition == null) return false; + return mCollectingTransition.hasOrderChanges(); + } + /** * Collects the window containers which need to be synced with the changing display area into * the current collecting transition. diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 208df6c768bf..2af656942a2a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -23,7 +23,9 @@ import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; @@ -1468,6 +1470,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } + case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: { + final Task task = taskFragment.getTask(); + task.moveOrCreateDecorSurfaceFor(taskFragment); + break; + } + case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: { + final Task task = taskFragment.getTask(); + task.removeDecorSurface(); + break; + } } return effects; } @@ -1507,6 +1519,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + // TODO (b/293654166) remove the decor surface checks once we clear security reviews + if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE + || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE" + + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE." + ); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + opType, exception); + return false; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, 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 cca4261795ab..c625b1e1eef7 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -159,6 +159,12 @@ <xs:element type="usiVersion" name="usiVersion"> <xs:annotation name="final"/> </xs:element> + <!-- Maximum screen brightness setting when screen brightness capped in + Wear Bedtime mode. This must be a non-negative decimal within the range defined by + the first and the last brightness value in screenBrightnessMap. --> + <xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> @@ -586,42 +592,39 @@ minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> - <!-- Sets the brightness mapping of the desired screen brightness in nits to the - corresponding lux for the current display --> - <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping" + <!-- Sets the brightness mapping of the desired screen brightness to the corresponding + lux for the current display --> + <xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping" minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> </xs:sequence> </xs:complexType> - <!-- Represents the brightness mapping of the desired screen brightness in nits to the - corresponding lux for the current display --> - <xs:complexType name="displayBrightnessMapping"> - <xs:sequence> - <!-- Sets the list of display brightness points, each representing the desired screen - brightness in nits to the corresponding lux for the current display - - The N entries of this array define N + 1 control points as follows: - (1-based arrays) - - Point 1: (0, nits[1]): currentLux <= 0 - Point 2: (lux[1], nits[2]): 0 < currentLux <= lux[1] - Point 3: (lux[2], nits[3]): lux[2] < currentLux <= lux[3] - ... - Point N+1: (lux[N], nits[N+1]): lux[N] < currentLux - - The control points must be strictly increasing. Each control point - corresponds to an entry in the brightness backlight values arrays. - For example, if currentLux == lux[1] (first element of the levels array) - then the brightness will be determined by nits[2] (second element - of the brightness values array). - --> - <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint" - minOccurs="1" maxOccurs="unbounded"> - <xs:annotation name="final"/> - </xs:element> - </xs:sequence> + <!-- Sets the list of display brightness points, each representing the desired screen brightness + in a certain lux environment. + + The first value of each point is the lux value and the second value is the brightness value. + + The first lux value must be 0. + + The control points must be strictly increasing. + + Example: if currentLux == the second lux value in the mapping then the brightness will be + determined by the second brightness value in the mapping. Spline interpolation is used + to determine the auto-brightness values for lux levels between these control points. + + The brightness values must be non-negative decimals within the range between the first and + the last brightness values in screenBrightnessMap. + + This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues + defined in the config XML resource. + --> + <xs:complexType name="luxToBrightnessMapping"> + <xs:element name="map" type="nonNegativeFloatToFloatMap"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> </xs:complexType> <!-- Represents a point in the display brightness mapping, representing the lux level from the diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index f767291b4953..8c8c1230f944 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -7,14 +7,14 @@ package com.android.server.display.config { method public final java.math.BigInteger getBrighteningLightDebounceMillis(); method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis(); method public final java.math.BigInteger getDarkeningLightDebounceMillis(); - method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping(); method public boolean getEnabled(); + method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping(); method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger); method public final void setBrighteningLightDebounceMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceMillis(java.math.BigInteger); - method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping); method public void setEnabled(boolean); + method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping); } public class BlockingZoneConfig { @@ -78,11 +78,6 @@ package com.android.server.display.config { method public java.util.List<com.android.server.display.config.Density> getDensity(); } - public class DisplayBrightnessMapping { - ctor public DisplayBrightnessMapping(); - method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint(); - } - public class DisplayBrightnessPoint { ctor public DisplayBrightnessPoint(); method public final java.math.BigInteger getLux(); @@ -110,6 +105,7 @@ package com.android.server.display.config { method public final com.android.server.display.config.SensorDetails getProxSensor(); method public com.android.server.display.config.DisplayQuirks getQuirks(); method public com.android.server.display.config.RefreshRateConfigs getRefreshRate(); + method public final java.math.BigDecimal getScreenBrightnessCapForWearBedtimeMode(); method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault(); method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap(); method public final java.math.BigInteger getScreenBrightnessRampDecreaseMaxIdleMillis(); @@ -143,6 +139,7 @@ package com.android.server.display.config { method public final void setProxSensor(com.android.server.display.config.SensorDetails); method public void setQuirks(com.android.server.display.config.DisplayQuirks); method public void setRefreshRate(com.android.server.display.config.RefreshRateConfigs); + method public final void setScreenBrightnessCapForWearBedtimeMode(java.math.BigDecimal); method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal); method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap); method public final void setScreenBrightnessRampDecreaseMaxIdleMillis(java.math.BigInteger); @@ -220,6 +217,12 @@ package com.android.server.display.config { method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap(); } + public class LuxToBrightnessMapping { + ctor public LuxToBrightnessMapping(); + method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap(); + method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap); + } + public class NitsMap { ctor public NitsMap(); method public String getInterpolation(); diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 1988bb6e6e46..da44aac5826a 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -23,12 +23,14 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.credentials.CredentialProviderInfo; +import android.credentials.flags.Flags; import android.credentials.ui.ProviderData; import android.credentials.ui.UserSelectionDialogResult; import android.os.Binder; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; +import android.os.IInterface; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; @@ -94,6 +96,9 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential private final Set<ComponentName> mEnabledProviders; + private final RequestSessionDeathRecipient mDeathRecipient = + new RequestSessionDeathRecipient(); + protected PendingIntent mPendingIntent; @NonNull @@ -141,11 +146,26 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType)); setCancellationListener(); + if (Flags.clearSessionEnabled()) { + setUpClientCallbackListener(); + } + } + + private void setUpClientCallbackListener() { + if (mClientCallback != null && mClientCallback instanceof IInterface) { + IInterface callback = (IInterface) mClientCallback; + try { + callback.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + Slog.e(TAG, e.getMessage()); + } + } } private void setCancellationListener() { mCancellationSignal.setOnCancelListener( () -> { + Slog.d(TAG, "Cancellation invoked from the client - clearing session"); boolean isUiActive = maybeCancelUi(); finishSession(!isUiActive); } @@ -168,6 +188,17 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return false; } + private boolean isUiWaitingForData() { + // Technically, the status can also be IN_PROGRESS when the user has made a selection + // so this an over estimation, but safe to do so as it is used for cancellation + // propagation to the provider in a very narrow time frame. If provider has + // already responded, cancellation is not an issue as the cancellation listener + // is independent of the service binding. + // TODO(b/313512500): Do not propagate cancelation if provider has responded in + // query phase. + return mCredentialManagerUi.getStatus() == CredentialManagerUi.UiStatus.IN_PROGRESS; + } + public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo, RemoteCredentialService remoteCredentialService); @@ -373,4 +404,12 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return chosenProviderSession != null && chosenProviderSession.mProviderInfo != null && chosenProviderSession.mProviderInfo.isPrimary(); } + + private class RequestSessionDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + Slog.d(TAG, "Client binder died - clearing session"); + finishSession(isUiWaitingForData()); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0a2e80606e96..1919eb33c38c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2112,6 +2112,15 @@ public final class SystemServer implements Dumpable { networkPolicy.bindConnectivityManager(); t.traceEnd(); + t.traceBegin("StartSecurityStateManagerService"); + try { + ServiceManager.addService(Context.SECURITY_STATE_SERVICE, + new SecurityStateManagerService(context)); + } catch (Throwable e) { + reportWtf("starting SecurityStateManagerService", e); + } + t.traceEnd(); + t.traceBegin("StartVpnManagerService"); try { vpnManager = VpnManagerService.create(context); diff --git a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java b/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java deleted file mode 100644 index 0ad418427183..000000000000 --- a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2023 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.media; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.when; - -import android.app.Application; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; -import android.content.Context; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaRoute2Info; -import android.os.UserHandle; - -import androidx.test.core.app.ApplicationProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.Shadows; -import org.robolectric.shadows.ShadowBluetoothAdapter; -import org.robolectric.shadows.ShadowBluetoothDevice; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -@RunWith(RobolectricTestRunner.class) -public class AudioPoliciesBluetoothRouteControllerTest { - - private static final String DEVICE_ADDRESS_UNKNOWN = ":unknown:ip:address:"; - private static final String DEVICE_ADDRESS_SAMPLE_1 = "30:59:8B:E4:C6:35"; - private static final String DEVICE_ADDRESS_SAMPLE_2 = "0D:0D:A6:FF:8D:B6"; - private static final String DEVICE_ADDRESS_SAMPLE_3 = "2D:9B:0C:C2:6F:78"; - private static final String DEVICE_ADDRESS_SAMPLE_4 = "66:88:F9:2D:A8:1E"; - - private Context mContext; - - private ShadowBluetoothAdapter mShadowBluetoothAdapter; - - @Mock - private BluetoothRouteController.BluetoothRoutesUpdatedListener mListener; - - @Mock - private BluetoothProfileMonitor mBluetoothProfileMonitor; - - private AudioPoliciesBluetoothRouteController mAudioPoliciesBluetoothRouteController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - Application application = ApplicationProvider.getApplicationContext(); - mContext = application; - - BluetoothManager bluetoothManager = (BluetoothManager) - mContext.getSystemService(Context.BLUETOOTH_SERVICE); - - BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); - mShadowBluetoothAdapter = Shadows.shadowOf(bluetoothAdapter); - - mAudioPoliciesBluetoothRouteController = - new AudioPoliciesBluetoothRouteController(mContext, bluetoothAdapter, - mBluetoothProfileMonitor, mListener) { - @Override - boolean isDeviceConnected(BluetoothDevice device) { - return true; - } - }; - - // Enable A2DP profile. - when(mBluetoothProfileMonitor.isProfileSupported(eq(BluetoothProfile.A2DP), any())) - .thenReturn(true); - mShadowBluetoothAdapter.setProfileConnectionState(BluetoothProfile.A2DP, - BluetoothProfile.STATE_CONNECTED); - - mAudioPoliciesBluetoothRouteController.start(UserHandle.of(0)); - } - - @Test - public void getSelectedRoute_noBluetoothRoutesAvailable_returnsNull() { - assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull(); - } - - @Test - public void selectRoute_noBluetoothRoutesAvailable_returnsFalse() { - assertThat(mAudioPoliciesBluetoothRouteController - .selectRoute(DEVICE_ADDRESS_UNKNOWN)).isFalse(); - } - - @Test - public void selectRoute_noDeviceWithGivenAddress_returnsFalse() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_3); - - mShadowBluetoothAdapter.setBondedDevices(devices); - - assertThat(mAudioPoliciesBluetoothRouteController - .selectRoute(DEVICE_ADDRESS_SAMPLE_2)).isFalse(); - } - - @Test - public void selectRoute_deviceIsInDevicesSet_returnsTrue() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2); - - mShadowBluetoothAdapter.setBondedDevices(devices); - - assertThat(mAudioPoliciesBluetoothRouteController - .selectRoute(DEVICE_ADDRESS_SAMPLE_1)).isTrue(); - } - - @Test - public void selectRoute_resetSelectedDevice_returnsTrue() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2); - - mShadowBluetoothAdapter.setBondedDevices(devices); - - mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_1); - assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue(); - } - - @Test - public void selectRoute_noSelectedDevice_returnsTrue() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2); - - mShadowBluetoothAdapter.setBondedDevices(devices); - - assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue(); - } - - @Test - public void getSelectedRoute_updateRouteFailed_returnsNull() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2); - - mShadowBluetoothAdapter.setBondedDevices(devices); - mAudioPoliciesBluetoothRouteController - .selectRoute(DEVICE_ADDRESS_SAMPLE_3); - - assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull(); - } - - @Test - public void getSelectedRoute_updateRouteSuccessful_returnsUpdateDevice() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4); - - assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull(); - - mShadowBluetoothAdapter.setBondedDevices(devices); - - assertThat(mAudioPoliciesBluetoothRouteController - .selectRoute(DEVICE_ADDRESS_SAMPLE_4)).isTrue(); - - MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute(); - assertThat(selectedRoute.getAddress()).isEqualTo(DEVICE_ADDRESS_SAMPLE_4); - } - - @Test - public void getSelectedRoute_resetSelectedRoute_returnsNull() { - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet( - DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4); - - mShadowBluetoothAdapter.setBondedDevices(devices); - - // Device is not null now. - mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4); - // Rest the device. - mAudioPoliciesBluetoothRouteController.selectRoute(null); - - assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()) - .isNull(); - } - - @Test - public void getTransferableRoutes_noSelectedRoute_returnsAllBluetoothDevices() { - String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1, - DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 }; - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses); - mShadowBluetoothAdapter.setBondedDevices(devices); - - // Force route controller to update bluetooth devices list. - sendBluetoothDevicesChangedBroadcast(); - - Set<String> transferableDevices = extractAddressesListFrom( - mAudioPoliciesBluetoothRouteController.getTransferableRoutes()); - assertThat(transferableDevices).containsExactlyElementsIn(addresses); - } - - @Test - public void getTransferableRoutes_hasSelectedRoute_returnsRoutesWithoutSelectedDevice() { - String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1, - DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 }; - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses); - mShadowBluetoothAdapter.setBondedDevices(devices); - - // Force route controller to update bluetooth devices list. - sendBluetoothDevicesChangedBroadcast(); - mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4); - - Set<String> transferableDevices = extractAddressesListFrom( - mAudioPoliciesBluetoothRouteController.getTransferableRoutes()); - assertThat(transferableDevices).containsExactly(DEVICE_ADDRESS_SAMPLE_1, - DEVICE_ADDRESS_SAMPLE_2); - } - - @Test - public void getAllBluetoothRoutes_hasSelectedRoute_returnsAllRoutes() { - String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1, - DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 }; - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses); - mShadowBluetoothAdapter.setBondedDevices(devices); - - // Force route controller to update bluetooth devices list. - sendBluetoothDevicesChangedBroadcast(); - mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4); - - Set<String> bluetoothDevices = extractAddressesListFrom( - mAudioPoliciesBluetoothRouteController.getAllBluetoothRoutes()); - assertThat(bluetoothDevices).containsExactlyElementsIn(addresses); - } - - @Test - public void updateVolumeForDevice_setVolumeForA2DPTo25_selectedRouteVolumeIsUpdated() { - String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1, - DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 }; - Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses); - mShadowBluetoothAdapter.setBondedDevices(devices); - - // Force route controller to update bluetooth devices list. - sendBluetoothDevicesChangedBroadcast(); - mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4); - - mAudioPoliciesBluetoothRouteController.updateVolumeForDevices( - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, 25); - - MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute(); - assertThat(selectedRoute.getVolume()).isEqualTo(25); - } - - private void sendBluetoothDevicesChangedBroadcast() { - Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); - mContext.sendBroadcast(intent); - } - - private static Set<String> extractAddressesListFrom(Collection<MediaRoute2Info> routes) { - Set<String> addresses = new HashSet<>(); - - for (MediaRoute2Info route: routes) { - addresses.add(route.getAddress()); - } - - return addresses; - } - - private static Set<BluetoothDevice> generateFakeBluetoothDevicesSet(String... addresses) { - Set<BluetoothDevice> devices = new HashSet<>(); - - for (String address: addresses) { - devices.add(ShadowBluetoothDevice.newInstance(address)); - } - - return devices; - } -} diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 97e582607133..a2e80f0d9b9b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -104,7 +104,7 @@ public class BrightnessMappingStrategyTest { 468.5f, }; - private static final int[] DISPLAY_LEVELS_BACKLIGHT = { + private static final int[] DISPLAY_LEVELS_INT = { 9, 30, 45, @@ -118,6 +118,20 @@ public class BrightnessMappingStrategyTest { 255 }; + private static final float[] DISPLAY_LEVELS = { + 0.03f, + 0.11f, + 0.17f, + 0.24f, + 0.3f, + 0.37f, + 0.46f, + 0.57f, + 0.7f, + 0.87f, + 1 + }; + private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f }; @@ -155,23 +169,23 @@ public class BrightnessMappingStrategyTest { DisplayWhiteBalanceController mMockDwbc; @Test - public void testSimpleStrategyMappingAtControlPoints() { - Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); + public void testSimpleStrategyMappingAtControlPoints_IntConfig() { + Resources res = createResources(DISPLAY_LEVELS_INT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 0; i < LUX_LEVELS.length; i++) { final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]); + PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]); assertEquals(expectedLevel, simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/); } } @Test - public void testSimpleStrategyMappingBetweenControlPoints() { - Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); + public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() { + Resources res = createResources(DISPLAY_LEVELS_INT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", simple); @@ -179,14 +193,42 @@ public class BrightnessMappingStrategyTest { final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON; assertTrue("Desired brightness should be between adjacent control points.", - backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1] - && backlight < DISPLAY_LEVELS_BACKLIGHT[i]); + backlight > DISPLAY_LEVELS_INT[i - 1] + && backlight < DISPLAY_LEVELS_INT[i]); + } + } + + @Test + public void testSimpleStrategyMappingAtControlPoints_FloatConfig() { + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS, + EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); + assertNotNull("BrightnessMappingStrategy should not be null", simple); + for (int i = 0; i < LUX_LEVELS.length; i++) { + assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), + /* tolerance= */ 0.0001f); + } + } + + @Test + public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() { + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS, + EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); + assertNotNull("BrightnessMappingStrategy should not be null", simple); + for (int i = 1; i < LUX_LEVELS.length; i++) { + final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; + final float brightness = simple.getBrightness(lux); + assertTrue("Desired brightness should be between adjacent control points.", + brightness > DISPLAY_LEVELS[i - 1] && brightness < DISPLAY_LEVELS[i]); } } @Test public void testSimpleStrategyIgnoresNewConfiguration() { - Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_INT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); @@ -201,14 +243,14 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNullConfiguration() { - Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_INT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); strategy.setBrightnessConfiguration(null); - final int n = DISPLAY_LEVELS_BACKLIGHT.length; + final int n = DISPLAY_LEVELS_INT.length; final float expectedBrightness = - (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON; + (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON; assertEquals(expectedBrightness, strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/); } @@ -322,7 +364,7 @@ public class BrightnessMappingStrategyTest { @Test public void testDefaultStrategyIsPhysical() { - Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_INT); DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); @@ -363,13 +405,13 @@ public class BrightnessMappingStrategyTest { BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); - res = createResources(DISPLAY_LEVELS_BACKLIGHT); + res = createResources(DISPLAY_LEVELS_INT); strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); // Extra backlight level final int[] backlight = Arrays.copyOf( - DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1); + DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; res = createResources(backlight); ddc = createDdc(DISPLAY_RANGE_NITS, @@ -410,7 +452,7 @@ public class BrightnessMappingStrategyTest { LUX_LEVELS, DISPLAY_LEVELS_NITS); assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc)); ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); - res = createResources(DISPLAY_LEVELS_BACKLIGHT); + res = createResources(DISPLAY_LEVELS_INT); assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc)); } @@ -546,16 +588,24 @@ public class BrightnessMappingStrategyTest { when(mockDdc.getBrightness()).thenReturn(backlightArray); when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS); when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY); + when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY); return mockDdc; } private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray, float[] luxLevelsFloat, float[] brightnessLevelsNits) { + return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits, + EMPTY_FLOAT_ARRAY); + } + + private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray, + float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) { DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); when(mockDdc.getNits()).thenReturn(nitsArray); when(mockDdc.getBrightness()).thenReturn(backlightArray); when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat); when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits); + when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels); return mockDdc; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 0bcbeb9b8a85..31d7e88e671b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -47,8 +47,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.ThermalStatus; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.Before; import org.junit.Test; @@ -92,10 +94,14 @@ public final class DisplayDeviceConfigTest { @Mock private Resources mResources; + @Mock + private DisplayManagerFlags mFlags; + @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getResources()).thenReturn(mResources); + when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true); mockDeviceConfigs(); } @@ -110,10 +116,6 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA); - assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new - float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA); - assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new - float[]{45.32f, 75.43f}, ZERO_DELTA); // Test thresholds assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), @@ -606,7 +608,7 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new - float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA); + float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA); // Test thresholds assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA); @@ -671,6 +673,9 @@ public final class DisplayDeviceConfigTest { assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type); assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); + + assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35), + mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); } @Test @@ -716,6 +721,38 @@ public final class DisplayDeviceConfigTest { assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle(), 0.04f, ZERO_DELTA); } + @Test + public void testBrightnessCapForWearBedtimeMode() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), + getValidProxSensor(), /* includeIdleMode= */ false)); + assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); + } + + @Test + public void testAutoBrightnessBrighteningLevels() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), + getValidProxSensor(), /* includeIdleMode= */ false)); + + assertArrayEquals(new float[]{0.0f, 80}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA); + assertArrayEquals(new float[]{0.2f, 0.3f}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA); + } + + @Test + public void testAutoBrightnessBrighteningLevels_FeatureFlagOff() throws IOException { + when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false); + setupDisplayDeviceConfigFromConfigResourceFile(); + setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), + getValidProxSensor(), /* includeIdleMode= */ false)); + + assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels()); + assertArrayEquals(new float[]{0, 110, 500}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA); + assertArrayEquals(new float[]{2, 200, 600}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA); + } + private String getValidLuxThrottling() { return "<luxThrottling>\n" + " <brightnessLimitMap>\n" @@ -1037,8 +1074,8 @@ public final class DisplayDeviceConfigTest { + "<screenBrightnessRampDecreaseMaxIdleMillis>" + "5000" + "</screenBrightnessRampDecreaseMaxIdleMillis>\n"; - } + private String getContent() { return getContent(getValidLuxThrottling(), getValidProxSensor(), /* includeIdleMode= */ true); @@ -1089,16 +1126,18 @@ public final class DisplayDeviceConfigTest { + "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n" + "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n" + (includeIdleMode ? getRampSpeedsIdle() : "") - + "<displayBrightnessMapping>\n" - + "<displayBrightnessPoint>\n" - + "<lux>50</lux>\n" - + "<nits>45.32</nits>\n" - + "</displayBrightnessPoint>\n" - + "<displayBrightnessPoint>\n" - + "<lux>80</lux>\n" - + "<nits>75.43</nits>\n" - + "</displayBrightnessPoint>\n" - + "</displayBrightnessMapping>\n" + + "<luxToBrightnessMapping>\n" + + "<map>\n" + + "<point>\n" + + "<first>0</first>\n" + + "<second>0.2</second>\n" + + "</point>\n" + + "<point>\n" + + "<first>80</first>\n" + + "<second>0.3</second>\n" + + "</point>\n" + + "</map>\n" + + "</luxToBrightnessMapping>\n" + "</autoBrightness>\n" + getPowerThrottlingConfig() + "<highBrightnessMode enabled=\"true\">\n" @@ -1355,6 +1394,9 @@ public final class DisplayDeviceConfigTest { + "<majorVersion>2</majorVersion>\n" + "<minorVersion>0</minorVersion>\n" + "</usiVersion>\n" + + "<screenBrightnessCapForWearBedtimeMode>" + + "0.1" + + "</screenBrightnessCapForWearBedtimeMode>" + "</displayConfiguration>\n"; } @@ -1372,7 +1414,7 @@ public final class DisplayDeviceConfigTest { private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException { Path tempFile = Files.createTempFile("display_config", ".tmp"); Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8)); - mDisplayDeviceConfig = new DisplayDeviceConfig(mContext); + mDisplayDeviceConfig = new DisplayDeviceConfig(mContext, mFlags); mDisplayDeviceConfig.initFromFile(tempFile.toFile()); } @@ -1381,25 +1423,15 @@ public final class DisplayDeviceConfigTest { when(mResources.obtainTypedArray( com.android.internal.R.array.config_screenBrightnessNits)) .thenReturn(screenBrightnessNits); - TypedArray screenBrightnessBacklight = createFloatTypedArray(new - float[]{0.0f, 120.0f, 255.0f}); - when(mResources.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessBacklight)) - .thenReturn(screenBrightnessBacklight); when(mResources.getIntArray(com.android.internal.R.array .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255}); - when(mResources.getIntArray(com.android.internal.R.array - .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80}); - when(mResources.getIntArray(com.android.internal.R.array - .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55}); - TypedArray screenBrightnessLevelNits = createFloatTypedArray(new float[]{2.0f, 200.0f, 600.0f}); when(mResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(screenBrightnessLevelNits); - int[] screenBrightnessLevelLux = new int[]{0, 110, 500}; + int[] screenBrightnessLevelLux = new int[]{110, 500}; when(mResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)) .thenReturn(screenBrightnessLevelLux); @@ -1457,7 +1489,12 @@ public final class DisplayDeviceConfigTest { R.integer.config_autoBrightnessDarkeningLightDebounce)) .thenReturn(4000); - mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true); + when(mResources.getInteger( + R.integer.config_screenBrightnessCapForWearBedtimeMode)) + .thenReturn(35); + + mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, /* useConfigXml= */ true, + mFlags); } private TypedArray createFloatTypedArray(float[] vals) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java index 4fd8f26d91a8..dc6abf1981c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java @@ -57,6 +57,9 @@ public class DisplayDeviceTest { @Mock private SurfaceControl.Transaction mMockTransaction; + @Mock + private DisplayAdapter mMockDisplayAdapter; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -67,34 +70,39 @@ public class DisplayDeviceTest { @Test public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() { - DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo); + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter); assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE); } @Test public void testGetDisplaySurfaceDefaultSizeLocked_rotation0() { - DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo); + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter); displayDevice.setProjectionLocked(mMockTransaction, ROTATION_0, new Rect(), new Rect()); assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE); } @Test public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() { - DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo); + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter); displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect()); assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE); } @Test public void testGetDisplaySurfaceDefaultSizeLocked_rotation180() { - DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo); + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter); displayDevice.setProjectionLocked(mMockTransaction, ROTATION_180, new Rect(), new Rect()); assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE); } @Test public void testGetDisplaySurfaceDefaultSizeLocked_rotation270() { - DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo); + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter); displayDevice.setProjectionLocked(mMockTransaction, ROTATION_270, new Rect(), new Rect()); assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE); } @@ -102,8 +110,9 @@ public class DisplayDeviceTest { private static class FakeDisplayDevice extends DisplayDevice { private final DisplayDeviceInfo mDisplayDeviceInfo; - FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo) { - super(null, null, "", InstrumentationRegistry.getInstrumentation().getContext()); + FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) { + super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "", + InstrumentationRegistry.getInstrumentation().getContext()); mDisplayDeviceInfo = displayDeviceInfo; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 55f56e93fea5..02e3ef4d5f0b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -212,7 +212,8 @@ public class DisplayManagerServiceTest { new DisplayManagerService.Injector() { @Override VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, - Context context, Handler handler, DisplayAdapter.Listener listener) { + Context context, Handler handler, DisplayAdapter.Listener listener, + DisplayManagerFlags flags) { return mMockVirtualDisplayAdapter; } @@ -251,7 +252,8 @@ public class DisplayManagerServiceTest { @Override VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, DisplayAdapter.Listener displayAdapterListener) { + Handler handler, DisplayAdapter.Listener displayAdapterListener, + DisplayManagerFlags flags) { return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, new VirtualDisplayAdapter.SurfaceControlDisplayFactory() { @Override @@ -263,7 +265,7 @@ public class DisplayManagerServiceTest { @Override public void destroyDisplay(IBinder displayToken) { } - }); + }, flags); } @Override @@ -329,6 +331,7 @@ public class DisplayManagerServiceTest { @Mock SensorManager mSensorManager; @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; @Mock PackageManagerInternal mMockPackageManagerInternal; + @Mock DisplayAdapter mMockDisplayAdapter; @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @Mock DisplayManagerFlags mMockFlags; @@ -788,7 +791,8 @@ public class DisplayManagerServiceTest { new DisplayManagerService.Injector() { @Override VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, - Context context, Handler handler, DisplayAdapter.Listener listener) { + Context context, Handler handler, DisplayAdapter.Listener listener, + DisplayManagerFlags flags) { return null; // return null for the adapter. This should cause a failure. } @@ -3194,7 +3198,7 @@ public class DisplayManagerServiceTest { private DisplayDeviceInfo mDisplayDeviceInfo; FakeDisplayDevice() { - super(null, null, "", mContext); + super(mMockDisplayAdapter, /* displayToken= */ null, /* uniqueId= */ "", mContext); } public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 8270845657c6..f36854b1ea78 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1455,8 +1455,8 @@ public class LocalDisplayAdapterTest { // mMockContext and values will be loaded from mMockResources. @Override public DisplayDeviceConfig createDisplayDeviceConfig(Context context, - long physicalDisplayId, boolean isFirstDisplay) { - return DisplayDeviceConfig.create(context, isFirstDisplay); + long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) { + return DisplayDeviceConfig.create(context, isFirstDisplay, flags); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 43d2b8f741ae..28ec89629df0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -119,8 +119,8 @@ public class LogicalDisplayMapperTest { @Mock IThermalService mIThermalServiceMock; @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE); - @Mock - DisplayManagerFlags mFlagsMock; + @Mock DisplayManagerFlags mFlagsMock; + @Mock DisplayAdapter mDisplayAdapterMock; @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; @Captor ArgumentCaptor<Integer> mDisplayEventCaptor; @@ -1075,7 +1075,8 @@ public class LogicalDisplayMapperTest { private int mState; TestDisplayDevice() { - super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock); + super(mDisplayAdapterMock, /* displayToken= */ null, + "test_display_" + sUniqueTestDisplayId++, mContextMock); mInfo = new DisplayDeviceInfo(); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java index 9f91916a4046..5676a388acff 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java @@ -32,9 +32,12 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -51,8 +54,12 @@ public class PersistentDataStoreTest { private TestInjector mInjector; private TestLooper mTestLooper; + @Mock + private DisplayAdapter mDisplayAdapter; + @Before public void setUp() { + MockitoAnnotations.initMocks(this); mInjector = new TestInjector(); mTestLooper = new TestLooper(); Handler handler = new Handler(mTestLooper.getLooper()); @@ -62,8 +69,8 @@ public class PersistentDataStoreTest { @Test public void testLoadBrightness() { final String uniqueDisplayId = "test:123"; - final DisplayDevice testDisplayDevice = new DisplayDevice( - null, null, uniqueDisplayId, null) { + final DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter, + /* displayToken= */ null, uniqueDisplayId, /* context= */ null) { @Override public boolean hasStableUniqueId() { return true; @@ -100,7 +107,8 @@ public class PersistentDataStoreTest { public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() { final String uniqueDisplayId = "test:123"; final DisplayDevice testDisplayDevice = - new DisplayDevice(null, null, uniqueDisplayId, null) { + new DisplayDevice(mDisplayAdapter, /* displayToken= */ null, uniqueDisplayId, + /* context= */ null) { @Override public boolean hasStableUniqueId() { return true; @@ -273,7 +281,8 @@ public class PersistentDataStoreTest { assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId, userSerial)); - DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter, + /* displayToken= */ null, uniqueDisplayId, /* context= */ null) { @Override public boolean hasStableUniqueId() { return true; @@ -319,7 +328,8 @@ public class PersistentDataStoreTest { assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId, userSerial)); - DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter, + /* displayToken= */ null, uniqueDisplayId, /* context= */ null) { @Override public boolean hasStableUniqueId() { return false; @@ -386,7 +396,8 @@ public class PersistentDataStoreTest { @Test public void testStoreAndRestoreResolution() { final String uniqueDisplayId = "test:123"; - DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter, + /* displayToken= */ null, uniqueDisplayId, /* context= */ null) { @Override public boolean hasStableUniqueId() { return true; @@ -422,7 +433,8 @@ public class PersistentDataStoreTest { @Test public void testStoreAndRestoreRefreshRate() { final String uniqueDisplayId = "test:123"; - DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter, + /* displayToken= */ null, uniqueDisplayId, /* context= */ null) { @Override public boolean hasStableUniqueId() { return true; @@ -455,7 +467,8 @@ public class PersistentDataStoreTest { @Test public void testBrightnessInitialisesWithInvalidFloat() { final String uniqueDisplayId = "test:123"; - DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) { + DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter, + /* displayToken= */ null, uniqueDisplayId, /* context= */ null) { @Override public boolean hasStableUniqueId() { return true; diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java index b7cbac5a7f18..5c50acb13f30 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java @@ -72,27 +72,14 @@ public class RefreshRateSettingsUtilsTest { @Test public void testFindHighestRefreshRateForDefaultDisplay() { - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); - assertEquals(120, + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); + assertEquals(DEFAULT_REFRESH_RATE, RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), /* delta= */ 0); - } - @Test - public void testFindHighestRefreshRate() { - int displayId = 13; - when(mDisplayManagerMock.getDisplay(displayId)).thenReturn(mDisplayMock); + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); assertEquals(120, - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, displayId), - /* delta= */ 0); - } - - @Test - public void testFindHighestRefreshRate_DisplayIsNull() { - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); - assertEquals(DEFAULT_REFRESH_RATE, RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), /* delta= */ 0); - } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index 73e7ba0750e0..c01b15c17483 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -28,6 +28,7 @@ import android.os.IBinder; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.testutils.TestHandler; import org.junit.Before; @@ -59,13 +60,17 @@ public class VirtualDisplayAdapterTest { private VirtualDisplayAdapter mVirtualDisplayAdapter; + @Mock + private DisplayManagerFlags mFeatureFlags; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mHandler = new TestHandler(null); mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(), - mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory); + mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory, + mFeatureFlags); when(mMockCallback.asBinder()).thenReturn(mMockBinder); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index ff2b1f466a5c..9aa6136348b4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -262,7 +262,8 @@ public class BrightnessClamperControllerTest { Handler handler, BrightnessClamperController.ClamperChangeListener clamperChangeListener, BrightnessClamperController.DisplayDeviceData data, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + Context context) { mCapturedChangeListener = clamperChangeListener; return mClampers; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java new file mode 100644 index 000000000000..3458b08245a2 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 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.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_OFF; +import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_ON; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.provider.Settings; +import android.testing.TestableContext; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class BrightnessWearBedtimeModeClamperTest { + + private static final float BRIGHTNESS_CAP = 0.3f; + + @Mock + private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + private final TestHandler mTestHandler = new TestHandler(null); + private final TestInjector mInjector = new TestInjector(); + private BrightnessWearBedtimeModeClamper mClamper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mClamper = new BrightnessWearBedtimeModeClamper(mInjector, mTestHandler, mContext, + mMockClamperChangeListener, () -> BRIGHTNESS_CAP); + mTestHandler.flush(); + } + + @Test + public void testBrightnessCap() { + assertEquals(BRIGHTNESS_CAP, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON); + } + + @Test + public void testBedtimeModeOn() { + setBedtimeModeEnabled(true); + assertTrue(mClamper.isActive()); + verify(mMockClamperChangeListener).onChanged(); + } + + @Test + public void testBedtimeModeOff() { + setBedtimeModeEnabled(false); + assertFalse(mClamper.isActive()); + verify(mMockClamperChangeListener).onChanged(); + } + + @Test + public void testType() { + assertEquals(BrightnessClamper.Type.BEDTIME_MODE, mClamper.getType()); + } + + @Test + public void testOnDisplayChanged() { + float newBrightnessCap = 0.61f; + + mClamper.onDisplayChanged(() -> newBrightnessCap); + mTestHandler.flush(); + + assertEquals(newBrightnessCap, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON); + verify(mMockClamperChangeListener).onChanged(); + } + + private void setBedtimeModeEnabled(boolean enabled) { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE, + enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF); + mInjector.notifyBedtimeModeChanged(); + mTestHandler.flush(); + } + + private static class TestInjector extends BrightnessWearBedtimeModeClamper.Injector { + + private ContentObserver mObserver; + + @Override + void registerBedtimeModeObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + mObserver = observer; + } + + private void notifyBedtimeModeChanged() { + if (mObserver != null) { + mObserver.dispatchChange(/* selfChange= */ false, + Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE)); + } + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index d0859232778d..60a0c039dd03 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -290,7 +290,6 @@ public class DisplayModeDirectorTest { }; private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY; - private static final int DISPLAY_ID_2 = Display.DEFAULT_DISPLAY + 1; private static final int MODE_ID = 1; private static final float TRANSITION_POINT = 0.763f; @@ -1551,12 +1550,9 @@ public class DisplayModeDirectorTest { public void testPeakRefreshRate_FlagEnabled() { when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) .thenReturn(true); - float highestRefreshRate1 = 130; - float highestRefreshRate2 = 132; - doReturn(highestRefreshRate1).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); - doReturn(highestRefreshRate2).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID_2)); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -1567,14 +1563,10 @@ public class DisplayModeDirectorTest { setPeakRefreshRate(Float.POSITIVE_INFINITY); - Vote vote1 = director.getVote(DISPLAY_ID, + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - Vote vote2 = director.getVote(DISPLAY_ID_2, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, - /* frameRateHigh= */ highestRefreshRate1); - assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, - /* frameRateHigh= */ highestRefreshRate2); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ + highestRefreshRate); } @Test @@ -1592,54 +1584,19 @@ public class DisplayModeDirectorTest { setPeakRefreshRate(peakRefreshRate); - Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, - /* frameRateHigh= */ peakRefreshRate); - } - - @Test - public void testPeakRefreshRate_DisplayChanged() { - when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) - .thenReturn(true); - float highestRefreshRate = 130; - doReturn(highestRefreshRate).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); - DisplayModeDirector director = - createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); - director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); - - Sensor lightSensor = createLightSensor(); - SensorManager sensorManager = createMockSensorManager(lightSensor); - director.start(sensorManager); - - setPeakRefreshRate(Float.POSITIVE_INFINITY); - - Vote vote = director.getVote(DISPLAY_ID, + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, - /* frameRateHigh= */ highestRefreshRate); - - // The highest refresh rate of the display changes - highestRefreshRate = 140; - doReturn(highestRefreshRate).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); - director.getDisplayObserver().onDisplayChanged(DISPLAY_ID); - - vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, - /* frameRateHigh= */ highestRefreshRate); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ + peakRefreshRate); } @Test public void testMinRefreshRate_FlagEnabled() { when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) .thenReturn(true); - float highestRefreshRate1 = 130; - float highestRefreshRate2 = 132; - doReturn(highestRefreshRate1).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); - doReturn(highestRefreshRate2).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID_2)); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -1650,12 +1607,9 @@ public class DisplayModeDirectorTest { setMinRefreshRate(Float.POSITIVE_INFINITY); - Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - Vote vote2 = director.getVote(DISPLAY_ID_2, + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ highestRefreshRate1, - /* frameRateHigh= */ Float.POSITIVE_INFINITY); - assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ highestRefreshRate2, + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, /* frameRateHigh= */ Float.POSITIVE_INFINITY); } @@ -1674,44 +1628,13 @@ public class DisplayModeDirectorTest { setMinRefreshRate(minRefreshRate); - Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate, /* frameRateHigh= */ Float.POSITIVE_INFINITY); } @Test - public void testMinRefreshRate_DisplayChanged() { - when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) - .thenReturn(true); - float highestRefreshRate = 130; - doReturn(highestRefreshRate).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); - DisplayModeDirector director = - createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); - director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); - - Sensor lightSensor = createLightSensor(); - SensorManager sensorManager = createMockSensorManager(lightSensor); - director.start(sensorManager); - - setMinRefreshRate(Float.POSITIVE_INFINITY); - - Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, - /* frameRateHigh= */ Float.POSITIVE_INFINITY); - - // The highest refresh rate of the display changes - highestRefreshRate = 140; - doReturn(highestRefreshRate).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); - director.getDisplayObserver().onDisplayChanged(DISPLAY_ID); - - vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, - /* frameRateHigh= */ Float.POSITIVE_INFINITY); - } - - @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); @@ -3406,7 +3329,7 @@ public class DisplayModeDirectorTest { public static class FakesInjector implements DisplayModeDirector.Injector { private final FakeDeviceConfig mDeviceConfig; private final DisplayInfo mDisplayInfo; - private final Map<Integer, Display> mDisplays; + private final Display mDisplay; private boolean mDisplayInfoValid = true; private final DisplayManagerInternal mDisplayManagerInternal; private final StatusBarManagerInternal mStatusBarManagerInternal; @@ -3427,8 +3350,7 @@ public class DisplayModeDirectorTest { mDisplayInfo.defaultModeId = MODE_ID; mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, 800, 600, /* refreshRate= */ 60)}; - mDisplays = Map.of(DISPLAY_ID, createDisplay(DISPLAY_ID), - DISPLAY_ID_2, createDisplay(DISPLAY_ID_2)); + mDisplay = createDisplay(DISPLAY_ID); mDisplayManagerInternal = displayManagerInternal; mStatusBarManagerInternal = statusBarManagerInternal; mSensorManagerInternal = sensorManagerInternal; @@ -3459,12 +3381,12 @@ public class DisplayModeDirectorTest { @Override public Display getDisplay(int displayId) { - return mDisplays.get(displayId); + return mDisplay; } @Override public Display[] getDisplays() { - return mDisplays.values().toArray(new Display[0]); + return new Display[] { mDisplay }; } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index be29163e7677..cbc8538cf9fb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -61,6 +62,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.hardware.Sensor; @@ -128,6 +130,8 @@ public class DeviceIdleControllerTest { @Mock private ConnectivityManager mConnectivityManager; @Mock + private ContentResolver mContentResolver; + @Mock private IActivityManager mIActivityManager; @Mock private LocationManager mLocationManager; @@ -332,7 +336,7 @@ public class DeviceIdleControllerTest { anyString(), any(Executor.class), any(DeviceConfig.OnPropertiesChangedListener.class))); doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock - -> mock(DeviceConfig.Properties.class)) + -> new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_DEVICE_IDLE).build()) .when(() -> DeviceConfig.getProperties( anyString(), ArgumentMatchers.<String>any())); when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock); @@ -347,6 +351,7 @@ public class DeviceIdleControllerTest { mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper()); mAnyMotionDetector = new AnyMotionDetectorForTest(); mInjector = new InjectorForTest(getContext()); + doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any()); doReturn(mWearModeManagerInternal) .when(() -> LocalServices.getService(WearModeManagerInternal.class)); @@ -366,7 +371,8 @@ public class DeviceIdleControllerTest { mDeviceIdleController.setLightEnabledForTest(true); // Get the same Constants object that mDeviceIdleController got. - mConstants = mInjector.getConstants(mDeviceIdleController); + mConstants = mInjector.getConstants(mDeviceIdleController, + mInjector.getHandler(mDeviceIdleController), mContentResolver); final ArgumentCaptor<TelephonyCallback> telephonyCallbackCaptor = ArgumentCaptor.forClass(TelephonyCallback.class); @@ -2164,9 +2170,8 @@ public class DeviceIdleControllerTest { public void testStationaryDetection_QuickDozeOff() { setQuickDozeEnabled(false); enterDeepState(STATE_IDLE); - // Regular progression through states, so time should have increased appropriately. - mInjector.nowElapsed += mConstants.IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.SENSING_TIMEOUT - + mConstants.LOCATING_TIMEOUT; + // Indicate that enough time has passed for the device to be considered stationary. + mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT; StationaryListenerForTest stationaryListener = new StationaryListenerForTest(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java new file mode 100644 index 000000000000..c5e6824099ed --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 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.gnss; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.content.ContentResolver; +import android.content.Context; +import android.location.GnssCapabilities; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.flags.Flags; +import android.location.provider.ProviderRequest; +import android.os.PowerManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; +import android.telephony.TelephonyManager; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; +import com.android.server.timedetector.TimeDetectorInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.quality.Strictness; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Presubmit +@androidx.test.filters.SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssLocationProviderTest { + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.WARN) + .mockStatic(Settings.Global.class) + .build(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private @Mock Context mContext; + private @Mock LocationManagerInternal mLocationManagerInternal; + private @Mock LocationManager mLocationManager; + private @Mock TimeDetectorInternal mTimeDetectorInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock GnssMetrics mGnssMetrics; + private @Mock PowerManager mPowerManager; + private @Mock TelephonyManager mTelephonyManager; + private @Mock AppOpsManager mAppOpsManager; + private @Mock AlarmManager mAlarmManager; + private @Mock PowerManager.WakeLock mWakeLock; + private @Mock ContentResolver mContentResolver; + private @Mock UserManager mUserManager; + private @Mock UserHandle mUserHandle; + private Set<UserHandle> mUserHandleSet = new HashSet<>(); + + private GnssNative mGnssNative; + + private GnssLocationProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn("mypackage").when(mContext).getPackageName(); + doReturn("attribution").when(mContext).getAttributionTag(); + doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); + doReturn(mPowerManager).when(mContext).getSystemService("power"); + doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); + doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE); + doReturn(mAlarmManager).when(mContext).getSystemService(Context.ALARM_SERVICE); + doReturn(mLocationManager).when(mContext).getSystemService(LocationManager.class); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); + mUserHandleSet.add(mUserHandle); + doReturn(true).when(mLocationManager).isLocationEnabledForUser(eq(mUserHandle)); + doReturn(mUserHandleSet).when(mUserManager).getVisibleUsers(); + doReturn(mContentResolver).when(mContext).getContentResolver(); + doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString()); + LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal); + LocalServices.addService(TimeDetectorInternal.class, mTimeDetectorInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull( + GnssNative.create(injector, mMockConfiguration))); + doReturn(true).when(mGnssNative).init(); + GnssCapabilities gnssCapabilities = new GnssCapabilities.Builder().setHasScheduling( + true).build(); + doReturn(gnssCapabilities).when(mGnssNative).getCapabilities(); + + mTestProvider = new GnssLocationProvider(mContext, mGnssNative, mGnssMetrics); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + LocalServices.removeServiceForTest(TimeDetectorInternal.class); + } + + @Test + public void testStartNavigating() { + ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis( + 0).build(); + + mTestProvider.onSetRequest(providerRequest); + verify(mGnssNative).start(); + } + + @Test + public void testUpdateRequirements_sameRequest() { + mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE); + ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis( + 0).build(); + + mTestProvider.onSetRequest(providerRequest); + verify(mGnssNative).start(); + + // set the same request + mTestProvider.onSetRequest(providerRequest); + verify(mGnssNative, never()).stop(); + verify(mGnssNative, times(1)).start(); + } + + @Test + public void testUpdateRequirements_differentRequest() { + mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE); + ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis( + 0).build(); + + mTestProvider.onSetRequest(providerRequest); + verify(mGnssNative).start(); + + // set a different request + providerRequest = new ProviderRequest.Builder().setIntervalMillis(2000).build(); + mTestProvider.onSetRequest(providerRequest); + verify(mGnssNative).stop(); + verify(mGnssNative, times(2)).start(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt index 931b38dc2951..f8fe97e9af13 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt @@ -22,12 +22,14 @@ import android.content.pm.PackageManager.PERMISSION_DENIED import android.content.pm.PackageManager.PERMISSION_GRANTED import android.content.pm.UserInfo import android.os.Build +import android.os.UserHandle import android.os.UserHandle.USER_SYSTEM import android.util.Log import com.android.server.testutils.any import com.android.server.testutils.spy import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Rule import org.junit.Test @@ -177,4 +179,13 @@ class DeletePackageHelperTest { assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_INTERNAL_ERROR) } + + @Test + fun deletePackageLIFWithNonExistantPackage_isFalse() { + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) + val result = dph.deletePackageLIF("a.nonexistent.package", UserHandle.of(USER_SYSTEM), true, + intArrayOf(0), 0, PackageRemovedInfo(), true) + assertFalse(result) + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 7148b164efa9..2e0ba0083850 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -82,6 +82,15 @@ public class BatteryUsageStatsProviderTest { .isEqualTo(20 * MINUTE_IN_MS); assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND)) .isEqualTo(40 * MINUTE_IN_MS); + assertThat(uidBatteryConsumer + .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND)) + .isEqualTo(20 * MINUTE_IN_MS); + assertThat(uidBatteryConsumer + .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND)) + .isEqualTo(20 * MINUTE_IN_MS); + assertThat(uidBatteryConsumer + .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE)) + .isEqualTo(20 * MINUTE_IN_MS); assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO)) .isWithin(PRECISION).of(2.0); assertThat( diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index 07c486c6ce58..079ea2c7832f 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java @@ -193,14 +193,14 @@ public class BatteryUsageStatsTest { for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { if (uidBatteryConsumer.getUid() == APP_UID1) { assertUidBatteryConsumer(uidBatteryConsumer, 2124, null, - 5321, 7432, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745, + 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745, POWER_MODEL_UNDEFINED, 956, 1167, 1478, true, 3554, 3776, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982, 444, 1110); } else if (uidBatteryConsumer.getUid() == APP_UID2) { assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar", - 1111, 2222, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444, + 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777, true, 1777, 1888, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991, @@ -269,7 +269,7 @@ public class BatteryUsageStatsTest { .setStatsEndTimestamp(3000); addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo", - 1000, 2000, + 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456); @@ -312,13 +312,13 @@ public class BatteryUsageStatsTest { .setStatsEndTimestamp(5000); addUidBatteryConsumer(builder, batteryStats, APP_UID1, null, - 4321, 5432, + 4321, 5400, 32, 123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_ENERGY_CONSUMPTION, 456, 567, 678, 1777, 7771, 1888, 8881, 1999, 9991, 321, 654); addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar", - 1111, 2222, + 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777, 1777, 7771, 1888, 8881, 1999, 9991, 321, 654); @@ -338,7 +338,8 @@ public class BatteryUsageStatsTest { private void addUidBatteryConsumer(BatteryUsageStats.Builder builder, MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain, - int timeInStateForeground, int timeInStateBackground, double screenPower, + int timeInProcessStateForeground, int timeInProcessStateBackground, + int timeInProcessStateForegroundService, double screenPower, int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower, int cpuDuration, int customComponentDuration, double cpuPowerForeground, int cpuDurationForeground, double cpuPowerBackground, int cpuDurationBackground, @@ -348,8 +349,10 @@ public class BatteryUsageStatsTest { builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid); uidBuilder .setPackageWithHighestDrain(packageWithHighestDrain) - .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, timeInStateForeground) - .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, timeInStateBackground) + .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, timeInProcessStateForeground) + .setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, timeInProcessStateBackground) + .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, + timeInProcessStateForegroundService) .setConsumedPower( BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel) .setConsumedPower( @@ -446,7 +449,7 @@ public class BatteryUsageStatsTest { for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { if (uidBatteryConsumer.getUid() == APP_UID1) { assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo", - 1000, 2000, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400, + 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800, true, 1777, 1888, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456); @@ -496,8 +499,9 @@ public class BatteryUsageStatsTest { } private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer, - double consumedPower, String packageWithHighestDrain, int timeInStateForeground, - int timeInStateBackground, int screenPower, int screenPowerModel, double cpuPower, + double consumedPower, String packageWithHighestDrain, int timeInProcessStateForeground, + int timeInProcessStateBackground, int timeInProcessStateForegroundService, + int screenPower, int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower, int cpuDuration, int customComponentDuration, boolean processStateDataIncluded, double totalPowerForeground, double totalPowerBackground, double totalPowerFgs, @@ -509,9 +513,16 @@ public class BatteryUsageStatsTest { assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo( packageWithHighestDrain); assertThat(uidBatteryConsumer.getTimeInStateMs( - UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(timeInStateForeground); + UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(timeInProcessStateForeground); assertThat(uidBatteryConsumer.getTimeInStateMs( - UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(timeInStateBackground); + UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo( + timeInProcessStateBackground + timeInProcessStateForegroundService); + assertThat(uidBatteryConsumer.getTimeInProcessStateMs( + PROCESS_STATE_FOREGROUND)).isEqualTo(timeInProcessStateForeground); + assertThat(uidBatteryConsumer.getTimeInProcessStateMs( + PROCESS_STATE_BACKGROUND)).isEqualTo(timeInProcessStateBackground); + assertThat(uidBatteryConsumer.getTimeInProcessStateMs( + PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(timeInProcessStateForegroundService); assertThat(uidBatteryConsumer.getConsumedPower( BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower); assertThat(uidBatteryConsumer.getPowerModel( diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index 1002fba3d60d..88ca02933450 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.app.ActivityManagerInternal; +import android.app.pinner.PinnedFileStat; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -47,22 +48,19 @@ import com.android.server.wm.ActivityTaskManagerInternal; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.io.BufferedReader; import java.io.CharArrayWriter; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Optional; +import java.util.List; import java.util.concurrent.TimeUnit; @SmallTest @@ -138,6 +136,13 @@ public class PinnerServiceTest { protected void publishBinderService(PinnerService service, Binder binderService) { // Suppress this for testing, it's not needed and causes conflitcs. } + + @Override + protected PinnerService.PinnedFile pinFileInternal(String fileToPin, + int maxBytesToPin, boolean attemptPinIntrospection) { + return new PinnerService.PinnedFile(-1, + maxBytesToPin, fileToPin, maxBytesToPin); + } }; } @@ -202,20 +207,27 @@ public class PinnerServiceTest { return cw.toString(); } - private int getPinnedSize(PinnerService pinnerService) throws Exception { - return getPinnedSizeImpl(pinnerService, "Total size: "); + private long getPinnedSize(PinnerService pinnerService) { + long totalBytesPinned = 0; + for (PinnedFileStat stat : pinnerService.getPinnerStats()) { + totalBytesPinned += stat.getBytesPinned(); + } + return totalBytesPinned; } - private int getPinnedAnonSize(PinnerService pinnerService) throws Exception { - return getPinnedSizeImpl(pinnerService, "Pinned anon region: "); + private int getPinnedAnonSize(PinnerService pinnerService) { + List<PinnedFileStat> anonStats = pinnerService.getPinnerStats().stream() + .filter(pf -> pf.getGroupName().equals(PinnerService.ANON_REGION_STAT_NAME)) + .toList(); + int totalAnon = 0; + for (PinnedFileStat anonStat : anonStats) { + totalAnon += anonStat.getBytesPinned(); + } + return totalAnon; } - private int getPinnedSizeImpl(PinnerService pinnerService, String sizeToken) throws Exception { - String dumpOutput = getPinnerServiceDump(pinnerService); - BufferedReader bufReader = new BufferedReader(new StringReader(dumpOutput)); - Optional<Integer> size = bufReader.lines().filter(s -> s.contains(sizeToken)) - .map(s -> Integer.valueOf(s.substring(sizeToken.length()))).findAny(); - return size.orElse(-1); + private long getTotalPinnedFiles(PinnerService pinnerService) { + return pinnerService.getPinnerStats().stream().count(); } private void setDeviceConfigPinnedAnonSize(long size) { @@ -227,7 +239,6 @@ public class PinnerServiceTest { } @Test - @Ignore("b/309853498, pinning home app can fail with ENOMEM") public void testPinHomeApp() throws Exception { // Enable HOME app pinning mContext.getOrCreateTestableResources() @@ -245,15 +256,13 @@ public class PinnerServiceTest { ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); assertThat(pinnedApps.get(KEY_HOME)).isNotNull(); - // Check if dump() reports total pinned bytes - int totalPinnedSizeBytes = getPinnedSize(pinnerService); - assertThat(totalPinnedSizeBytes).isGreaterThan(0); + assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); + assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0); unpinAll(pinnerService); } @Test - @Ignore("b/309853498, pinning home app can fail with ENOMEM") public void testPinHomeAppOnBootCompleted() throws Exception { // Enable HOME app pinning mContext.getOrCreateTestableResources() @@ -271,9 +280,7 @@ public class PinnerServiceTest { ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); assertThat(pinnedApps.get(KEY_HOME)).isNotNull(); - // Check if dump() reports total pinned bytes - int totalPinnedSizeBytes = getPinnedSize(pinnerService); - assertThat(totalPinnedSizeBytes).isGreaterThan(0); + assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); unpinAll(pinnerService); } @@ -294,12 +301,24 @@ public class PinnerServiceTest { ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); assertThat(pinnedApps).isEmpty(); - // Check if dump() reports total pinned bytes - int totalPinnedSizeBytes = getPinnedSize(pinnerService); + long totalPinnedSizeBytes = getPinnedSize(pinnerService); assertThat(totalPinnedSizeBytes).isEqualTo(0); int pinnedAnonSizeBytes = getPinnedAnonSize(pinnerService); - assertThat(pinnedAnonSizeBytes).isEqualTo(-1); + assertThat(pinnedAnonSizeBytes).isEqualTo(0); + + unpinAll(pinnerService); + } + + @Test + public void testPinFile() throws Exception { + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + + pinnerService.pinFile("test_file", 4096, null, "my_group"); + + assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); + assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0); unpinAll(pinnerService); } @@ -341,11 +360,8 @@ public class PinnerServiceTest { waitForPinnerService(pinnerService); // An empty anon region should clear the associated status entry. pinnedAnonSizeBytes = getPinnedAnonSize(pinnerService); - assertThat(pinnedAnonSizeBytes).isEqualTo(-1); + assertThat(pinnedAnonSizeBytes).isEqualTo(0); unpinAll(pinnerService); } - - // TODO: Add test to check that the pages we expect to be pinned are actually pinned - } diff --git a/services/tests/servicestests/src/com/android/server/SecurityStateTest.java b/services/tests/servicestests/src/com/android/server/SecurityStateTest.java new file mode 100644 index 000000000000..fc91e47534f1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/SecurityStateTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 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; + +import static android.os.SecurityStateManager.KEY_KERNEL_VERSION; +import static android.os.SecurityStateManager.KEY_SYSTEM_SPL; +import static android.os.SecurityStateManager.KEY_VENDOR_SPL; + +import static com.android.server.SecurityStateManagerService.KERNEL_RELEASE_PATTERN; +import static com.android.server.SecurityStateManagerService.VENDOR_SECURITY_PATCH_PROPERTY_KEY; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.os.SystemProperties; +import android.os.VintfRuntimeInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.regex.Matcher; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SecurityStateTest { + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private Context mMockContext; + + @Mock + private PackageManager mMockPackageManager; + + @Mock + private Resources mMockResources; + + private static final String DEFAULT_MODULE_METADATA_PROVIDER = "com.android.modulemetadata"; + private static final String DEFAULT_MODULE_METADATA_PROVIDER_VERSION = "2023-12-01"; + private static final String DEFAULT_SECURITY_STATE_PACKAGE = "com.google.android.gms"; + private static final String DEFAULT_SECURITY_STATE_PACKAGE_VERSION = "2023-12-05"; + private static final String[] SECURITY_STATE_PACKAGES = + new String[]{DEFAULT_SECURITY_STATE_PACKAGE}; + + @Before + public void setUp() throws Exception { + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockContext.getString(R.string.config_defaultModuleMetadataProvider)) + .thenReturn(DEFAULT_MODULE_METADATA_PROVIDER); + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())) + .thenReturn(new PackageInfo()); + PackageInfo moduleMetadataPackageInfo = new PackageInfo(); + moduleMetadataPackageInfo.versionName = DEFAULT_MODULE_METADATA_PROVIDER_VERSION; + when(mMockPackageManager.getPackageInfo(DEFAULT_MODULE_METADATA_PROVIDER, 0)) + .thenReturn(moduleMetadataPackageInfo); + PackageInfo securityStatePackageInfo = new PackageInfo(); + securityStatePackageInfo.versionName = DEFAULT_SECURITY_STATE_PACKAGE_VERSION; + when(mMockPackageManager.getPackageInfo(DEFAULT_SECURITY_STATE_PACKAGE, 0)) + .thenReturn(securityStatePackageInfo); + when(mMockResources.getStringArray(R.array.config_securityStatePackages)) + .thenReturn(SECURITY_STATE_PACKAGES); + } + + @Test + public void testGetGlobalSecurityState_returnsBundle() { + SecurityStateManagerService securityState = new SecurityStateManagerService(mMockContext); + + Bundle bundle = securityState.getGlobalSecurityState(); + + assertEquals(bundle.getString(KEY_SYSTEM_SPL), Build.VERSION.SECURITY_PATCH); + assertEquals(bundle.getString(KEY_VENDOR_SPL), + SystemProperties.get(VENDOR_SECURITY_PATCH_PROPERTY_KEY, "")); + Matcher matcher = KERNEL_RELEASE_PATTERN.matcher(VintfRuntimeInfo.getKernelVersion()); + String kernelVersion = ""; + if (matcher.matches()) { + kernelVersion = matcher.group(1); + } + assertEquals(bundle.getString(KEY_KERNEL_VERSION), kernelVersion); + assertEquals(bundle.getString(DEFAULT_MODULE_METADATA_PROVIDER), + DEFAULT_MODULE_METADATA_PROVIDER_VERSION); + assertEquals(bundle.getString(DEFAULT_SECURITY_STATE_PACKAGE), + DEFAULT_SECURITY_STATE_PACKAGE_VERSION); + } +} diff --git a/services/tests/servicestests/src/com/android/server/WatchdogTest.java b/services/tests/servicestests/src/com/android/server/WatchdogTest.java new file mode 100644 index 000000000000..34ac47e1fe96 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/WatchdogTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.os.Handler; +import android.os.SimpleClock; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.Watchdog.HandlerChecker; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.ZoneOffset; + +/** Test class for {@link Watchdog}. */ +@RunWith(AndroidJUnit4.class) +public class WatchdogTest { + private static final int TIMEOUT_MS = 10; + + private TestClock mClock; + private Handler mHandler; + private HandlerChecker mChecker; + + @Before + public void setUp() { + mClock = new TestClock(); + mHandler = mock(Handler.class); + mChecker = + new HandlerChecker(mHandler, "monitor thread", new Object(), mClock) { + @Override + public boolean isHandlerPolling() { + return false; + } + }; + } + + @Test + public void checkerPausedUntilResume() { + Watchdog.Monitor monitor = mock(Watchdog.Monitor.class); + mChecker.addMonitorLocked(monitor); + + mChecker.pauseLocked("pausing"); + mChecker.scheduleCheckLocked(TIMEOUT_MS); + verifyNoMoreInteractions(mHandler); + assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked()); + + mChecker.resumeLocked("resuming"); + mChecker.scheduleCheckLocked(10); + assertEquals(Watchdog.WAITING, mChecker.getCompletionStateLocked()); + } + + @Test + public void checkerPausedUntilDeadline() { + Watchdog.Monitor monitor = mock(Watchdog.Monitor.class); + mChecker.addMonitorLocked(monitor); + + mChecker.pauseForLocked(10, "pausing"); + mChecker.scheduleCheckLocked(TIMEOUT_MS); + verifyNoMoreInteractions(mHandler); + assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked()); + + mClock.advanceBy(5); + verifyNoMoreInteractions(mHandler); + assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked()); + + // Above the 10s timeout. Watchdog should not be paused anymore. + mClock.advanceBy(6); + mChecker.scheduleCheckLocked(TIMEOUT_MS); + assertEquals(Watchdog.WAITING, mChecker.getCompletionStateLocked()); + } + + @Test + public void checkerPausedDuringScheduledRun() { + Watchdog.Monitor monitor = mock(Watchdog.Monitor.class); + mChecker.addMonitorLocked(monitor); + + mChecker.scheduleCheckLocked(TIMEOUT_MS); + mClock.advanceBy(5); + mChecker.pauseForLocked(10, "pausing"); + verifyNoMoreInteractions(mHandler); + assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked()); + + // Above the 10s timeout. Watchdog should not be paused anymore. + mClock.advanceBy(11); + mChecker.scheduleCheckLocked(TIMEOUT_MS); + assertEquals(Watchdog.WAITING, mChecker.getCompletionStateLocked()); + } + + @Test + public void blockedThread() { + mChecker.scheduleCheckLocked(TIMEOUT_MS); + assertEquals(mChecker.getCompletionStateLocked(), Watchdog.WAITING); + + mClock.advanceBy(6); + assertEquals(Watchdog.WAITED_UNTIL_PRE_WATCHDOG, mChecker.getCompletionStateLocked()); + + // Above the 10s timeout. + mClock.advanceBy(6); + assertEquals(Watchdog.OVERDUE, mChecker.getCompletionStateLocked()); + } + + @Test + public void checkNothingBlocked() { + Watchdog.Monitor monitor = mock(Watchdog.Monitor.class); + mChecker.addMonitorLocked(monitor); + + mChecker.scheduleCheckLocked(TIMEOUT_MS); + // scheduleCheckLocked calls #postAtFrontOfQueue which will call mChecker.run(). + mChecker.run(); + assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked()); + verify(monitor).monitor(); + } + + private static class TestClock extends SimpleClock { + long mNowMillis = 1; + + TestClock() { + super(ZoneOffset.UTC); + } + + @Override + public long millis() { + return mNowMillis; + } + + public void advanceBy(long millis) { + mNowMillis += millis; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 82efdd3ce40a..800350a7d326 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -85,9 +85,9 @@ import com.android.internal.compat.IPlatformCompat; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; import com.android.server.accessibility.magnification.FullScreenMagnificationController; +import com.android.server.accessibility.magnification.MagnificationConnectionManager; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationProcessor; -import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -156,7 +156,7 @@ public class AccessibilityManagerServiceTest { @Mock private UserManagerInternal mMockUserManagerInternal; @Mock private IBinder mMockBinder; @Mock private IAccessibilityServiceClient mMockServiceClient; - @Mock private WindowMagnificationManager mMockWindowMagnificationMgr; + @Mock private MagnificationConnectionManager mMockMagnificationConnectionManager; @Mock private MagnificationController mMockMagnificationController; @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; @Mock private ProxyManager mProxyManager; @@ -180,8 +180,8 @@ public class AccessibilityManagerServiceTest { UserManagerInternal.class, mMockUserManagerInternal); mInputFilter = Mockito.mock(FakeInputFilter.class); - when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( - mMockWindowMagnificationMgr); + when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn( + mMockMagnificationConnectionManager); when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( mMockFullScreenMagnificationController); when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true); @@ -530,7 +530,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr).requestConnection(true); + verify(mMockMagnificationConnectionManager).requestConnection(true); } @SmallTest @@ -547,7 +547,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr).requestConnection(true); + verify(mMockMagnificationConnectionManager).requestConnection(true); } @SmallTest @@ -565,7 +565,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr).requestConnection(false); + verify(mMockMagnificationConnectionManager).requestConnection(false); } @SmallTest @@ -583,7 +583,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr).requestConnection(true); + verify(mMockMagnificationConnectionManager).requestConnection(true); } @SmallTest @@ -602,7 +602,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr).requestConnection(false); + verify(mMockMagnificationConnectionManager).requestConnection(false); } @SmallTest @@ -616,7 +616,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr).requestConnection(true); + verify(mMockMagnificationConnectionManager).requestConnection(true); } @SmallTest @@ -630,7 +630,8 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt()); + verify(mMockMagnificationConnectionManager, atLeastOnce()) + .removeMagnificationButton(anyInt()); } @SmallTest @@ -644,7 +645,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt()); + verify(mMockMagnificationConnectionManager, never()).removeMagnificationButton(anyInt()); } @SmallTest @@ -659,7 +660,8 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt()); + verify(mMockMagnificationConnectionManager, atLeastOnce()) + .removeMagnificationButton(anyInt()); } @SmallTest @@ -674,7 +676,7 @@ public class AccessibilityManagerServiceTest { // Invokes client change to trigger onUserStateChanged. mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false); - verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt()); + verify(mMockMagnificationConnectionManager, never()).removeMagnificationButton(anyInt()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java index a02807fe766c..7829fcc4b44d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java @@ -39,9 +39,9 @@ import android.accessibilityservice.MagnificationConfig; import android.graphics.Region; import com.android.server.accessibility.magnification.FullScreenMagnificationController; +import com.android.server.accessibility.magnification.MagnificationConnectionManager; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationProcessor; -import com.android.server.accessibility.magnification.WindowMagnificationManager; import org.junit.Before; import org.junit.Test; @@ -66,21 +66,21 @@ public class MagnificationProcessorTest { @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; @Mock - private WindowMagnificationManager mMockWindowMagnificationManager; + private MagnificationConnectionManager mMockMagnificationConnectionManager; FullScreenMagnificationControllerStub mFullScreenMagnificationControllerStub; - WindowMagnificationManagerStub mWindowMagnificationManagerStub; + MagnificationManagerStub mMagnificationManagerStub; @Before public void setup() { MockitoAnnotations.initMocks(this); mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub( mMockFullScreenMagnificationController); - mWindowMagnificationManagerStub = new WindowMagnificationManagerStub( - mMockWindowMagnificationManager); + mMagnificationManagerStub = new MagnificationManagerStub( + mMockMagnificationConnectionManager); when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( mMockFullScreenMagnificationController); - when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( - mMockWindowMagnificationManager); + when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn( + mMockMagnificationConnectionManager); mMagnificationProcessor = new MagnificationProcessor(mMockMagnificationController); } @@ -194,7 +194,7 @@ public class MagnificationProcessorTest { doAnswer((invocation) -> { ((Region) invocation.getArguments()[1]).set(region); return null; - }).when(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY), + }).when(mMockMagnificationConnectionManager).getMagnificationSourceBounds(eq(TEST_DISPLAY), any()); final Region result = new Region(); @@ -286,7 +286,7 @@ public class MagnificationProcessorTest { mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false); - verify(mMockWindowMagnificationManager).disableWindowMagnification(TEST_DISPLAY, false, + verify(mMockMagnificationConnectionManager).disableWindowMagnification(TEST_DISPLAY, false, null); } @@ -296,7 +296,7 @@ public class MagnificationProcessorTest { mMagnificationProcessor.resetAllIfNeeded(connectionId); verify(mMockFullScreenMagnificationController).resetAllIfNeeded(eq(connectionId)); - verify(mMockWindowMagnificationManager).resetAllIfNeeded(eq(connectionId)); + verify(mMockMagnificationConnectionManager).resetAllIfNeeded(eq(connectionId)); } @Test @@ -450,7 +450,7 @@ public class MagnificationProcessorTest { .setActivated(false).build(); mMagnificationProcessor.setMagnificationConfig(TEST_DISPLAY, config, false, SERVICE_ID); - verify(mMockWindowMagnificationManager) + verify(mMockMagnificationConnectionManager) .disableWindowMagnification(eq(TEST_DISPLAY), anyBoolean()); } @@ -481,11 +481,11 @@ public class MagnificationProcessorTest { mFullScreenMagnificationControllerStub.resetAndStubMethods(); mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(), config.getCenterX(), config.getCenterY(), false, SERVICE_ID); - mWindowMagnificationManagerStub.deactivateIfNeed(); + mMagnificationManagerStub.deactivateIfNeed(); } else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) { - mWindowMagnificationManagerStub.resetAndStubMethods(); - mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(), - config.getCenterX(), config.getCenterY()); + mMagnificationManagerStub.resetAndStubMethods(); + mMockMagnificationConnectionManager.enableWindowMagnification( + displayId, config.getScale(), config.getCenterX(), config.getCenterY()); mFullScreenMagnificationControllerStub.deactivateIfNeed(); } } @@ -568,26 +568,26 @@ public class MagnificationProcessorTest { } } - private static class WindowMagnificationManagerStub { - private final WindowMagnificationManager mWindowMagnificationManager; + private static class MagnificationManagerStub { + private final MagnificationConnectionManager mMagnificationConnectionManager; private float mScale = 1.0f; private float mCenterX = 0; private float mCenterY = 0; private boolean mIsEnabled = false; - WindowMagnificationManagerStub( - WindowMagnificationManager windowMagnificationManager) { - mWindowMagnificationManager = windowMagnificationManager; + MagnificationManagerStub( + MagnificationConnectionManager magnificationConnectionManager) { + mMagnificationConnectionManager = magnificationConnectionManager; } private void stubMethods() { - doAnswer(invocation -> mScale).when(mWindowMagnificationManager).getScale( + doAnswer(invocation -> mScale).when(mMagnificationConnectionManager).getScale( TEST_DISPLAY); - doAnswer(invocation -> mCenterX).when(mWindowMagnificationManager).getCenterX( + doAnswer(invocation -> mCenterX).when(mMagnificationConnectionManager).getCenterX( TEST_DISPLAY); - doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY( + doAnswer(invocation -> mCenterY).when(mMagnificationConnectionManager).getCenterY( TEST_DISPLAY); - doAnswer(invocation -> mIsEnabled).when(mWindowMagnificationManager) + doAnswer(invocation -> mIsEnabled).when(mMagnificationConnectionManager) .isWindowMagnifierEnabled(TEST_DISPLAY); Answer enableWindowMagnificationStubAnswer = invocation -> { @@ -598,10 +598,10 @@ public class MagnificationProcessorTest { return true; }; doAnswer(enableWindowMagnificationStubAnswer).when( - mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY), + mMagnificationConnectionManager).enableWindowMagnification(eq(TEST_DISPLAY), anyFloat(), anyFloat(), anyFloat()); doAnswer(enableWindowMagnificationStubAnswer).when( - mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY), + mMagnificationConnectionManager).enableWindowMagnification(eq(TEST_DISPLAY), anyFloat(), anyFloat(), anyFloat(), any(), anyInt()); Answer disableWindowMagnificationStubAnswer = invocation -> { @@ -609,15 +609,15 @@ public class MagnificationProcessorTest { return true; }; doAnswer(disableWindowMagnificationStubAnswer).when( - mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY), + mMagnificationConnectionManager).disableWindowMagnification(eq(TEST_DISPLAY), anyBoolean()); doAnswer(disableWindowMagnificationStubAnswer).when( - mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY), + mMagnificationConnectionManager).disableWindowMagnification(eq(TEST_DISPLAY), anyBoolean(), any()); } public void resetAndStubMethods() { - Mockito.reset(mWindowMagnificationManager); + Mockito.reset(mMagnificationConnectionManager); stubMethods(); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index fd2cf6d4bb5f..3b39160643d1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -540,18 +540,23 @@ public class FullScreenMagnificationGestureHandlerTest { twoFingerTap(); assertIn(STATE_ACTIVATED); + verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); + verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true); } @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() { goFromStateIdleTo(STATE_ACTIVATED); + reset(mMockMagnificationLogger); twoFingerTap(); twoFingerTap(); twoFingerTap(); assertIn(STATE_IDLE); + verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); + verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(false); } @Test @@ -564,6 +569,8 @@ public class FullScreenMagnificationGestureHandlerTest { twoFingerTapAndHold(); assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); + verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); + verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true); } @Test @@ -576,6 +583,8 @@ public class FullScreenMagnificationGestureHandlerTest { twoFingerSwipeAndHold(); assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); + verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); + verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java index 24ad976f6e45..3843e2507df6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java @@ -33,8 +33,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static java.lang.Float.NaN; @@ -76,7 +74,7 @@ import org.mockito.invocation.InvocationOnMock; /** * Tests for WindowMagnificationManager. */ -public class WindowMagnificationManagerTest { +public class MagnificationConnectionManagerTest { private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM; private static final int SERVICE_ID = 1; @@ -91,9 +89,9 @@ public class WindowMagnificationManagerTest { @Mock private MagnificationAnimationCallback mAnimationCallback; @Mock - private WindowMagnificationManager.Callback mMockCallback; + private MagnificationConnectionManager.Callback mMockCallback; private MockContentResolver mResolver; - private WindowMagnificationManager mWindowMagnificationManager; + private MagnificationConnectionManager mMagnificationConnectionManager; @Before public void setUp() throws RemoteException { @@ -102,7 +100,7 @@ public class WindowMagnificationManagerTest { LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal); mResolver = new MockContentResolver(); mMockConnection = new MockWindowMagnificationConnection(); - mWindowMagnificationManager = new WindowMagnificationManager(mContext, new Object(), + mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(), mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext)); when(mContext.getContentResolver()).thenReturn(mResolver); @@ -122,11 +120,11 @@ public class WindowMagnificationManagerTest { final Context context = ApplicationProvider.getApplicationContext(); context.getMainThreadHandler().postDelayed( () -> { - mWindowMagnificationManager.setConnection( + mMagnificationConnectionManager.setConnection( connect ? mMockConnection.getConnection() : null); }, 10); } else { - mWindowMagnificationManager.setConnection( + mMagnificationConnectionManager.setConnection( connect ? mMockConnection.getConnection() : null); } return true; @@ -135,17 +133,17 @@ public class WindowMagnificationManagerTest { @Test public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - assertNotNull(mWindowMagnificationManager.mConnectionWrapper); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + assertTrue(mMagnificationConnectionManager.isConnected()); verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0)); } @Test public void setConnection_connectionIsNull_setMirrorWindowCallbackAndHasWrapper() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - assertNotNull(mWindowMagnificationManager.mConnectionWrapper); + assertTrue(mMagnificationConnectionManager.isConnected()); verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0)); verify(mMockConnection.getConnection()).setConnectionCallback( any(IWindowMagnificationConnectionCallback.class)); @@ -153,31 +151,31 @@ public class WindowMagnificationManagerTest { @Test public void binderDied_hasConnection_wrapperIsNullAndUnlinkToDeath() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); mMockConnection.getDeathRecipient().binderDied(); - assertNull(mWindowMagnificationManager.mConnectionWrapper); + assertFalse(mMagnificationConnectionManager.isConnected()); verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(), 0); } /** - * This test simulates {@link WindowMagnificationManager#setConnection} is called by thread A - * and then the former connection is called by thread B. In this situation we should keep the + * This test simulates {@link MagnificationConnectionManager#setConnection} is called by thread + * A and then the former connection is called by thread B. In this situation we should keep the * new connection. */ @Test public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); MockWindowMagnificationConnection secondConnection = new MockWindowMagnificationConnection(); - mWindowMagnificationManager.setConnection(secondConnection.getConnection()); + mMagnificationConnectionManager.setConnection(secondConnection.getConnection()); mMockConnection.getDeathRecipient().binderDied(); - assertNotNull(mWindowMagnificationManager.mConnectionWrapper); + assertTrue(mMagnificationConnectionManager.isConnected()); verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(), 0); verify(secondConnection.asBinder(), never()).unlinkToDeath( secondConnection.getDeathRecipient(), 0); @@ -185,20 +183,20 @@ public class WindowMagnificationManagerTest { @Test public void setNullConnection_hasConnection_wrapperIsNull() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.setConnection(null); + mMagnificationConnectionManager.setConnection(null); - assertNull(mWindowMagnificationManager.mConnectionWrapper); + assertFalse(mMagnificationConnectionManager.isConnected()); verify(mMockConnection.getConnection()).setConnectionCallback(null); } @Test public void enableWithAnimation_hasConnection_enableWindowMagnification() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), eq(200f), eq(300f), eq(0f), eq(0f), notNull()); @@ -207,9 +205,9 @@ public class WindowMagnificationManagerTest { @Test public void enableWithCallback_hasConnection_enableWindowMagnification() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f, + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f, mAnimationCallback, SERVICE_ID); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), @@ -221,10 +219,10 @@ public class WindowMagnificationManagerTest { @Test public void disable_hasConnectionAndEnabled_disableWindowMagnification() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY), notNull()); @@ -233,10 +231,10 @@ public class WindowMagnificationManagerTest { @Test public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false, + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false, mAnimationCallback); verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY), @@ -246,28 +244,28 @@ public class WindowMagnificationManagerTest { @Test public void isWindowMagnifierEnabled_hasConnectionAndEnabled_returnExpectedValue() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } @Test public void getPersistedScale() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - assertEquals(mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY), 2.5f); + assertEquals(mMagnificationConnectionManager.getPersistedScale(TEST_DISPLAY), 2.5f); } @Test public void persistScale_setValue_expectedValueInProvider() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN); - mWindowMagnificationManager.setScale(TEST_DISPLAY, 2.5f); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN); + mMagnificationConnectionManager.setScale(TEST_DISPLAY, 2.5f); - mWindowMagnificationManager.persistScale(TEST_DISPLAY); + mMagnificationConnectionManager.persistScale(TEST_DISPLAY); assertEquals(Settings.Secure.getFloatForUser(mResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f, @@ -276,11 +274,12 @@ public class WindowMagnificationManagerTest { @Test public void persistScale_setValueWhenScaleIsOne_nothingChanged() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - final float persistedScale = mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + final float persistedScale = + mMagnificationConnectionManager.getPersistedScale(TEST_DISPLAY); - mWindowMagnificationManager.setScale(TEST_DISPLAY, 1.0f); - mWindowMagnificationManager.persistScale(TEST_DISPLAY); + mMagnificationConnectionManager.setScale(TEST_DISPLAY, 1.0f); + mMagnificationConnectionManager.persistScale(TEST_DISPLAY); assertEquals(Settings.Secure.getFloatForUser(mResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f, @@ -289,50 +288,53 @@ public class WindowMagnificationManagerTest { @Test public void scaleSetterGetter_enabledOnTestDisplay_expectedValue() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN); - mWindowMagnificationManager.setScale(TEST_DISPLAY, 2.5f); + mMagnificationConnectionManager.setScale(TEST_DISPLAY, 2.5f); - assertEquals(mWindowMagnificationManager.getScale(TEST_DISPLAY), 2.5f); + assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY), 2.5f); } @Test public void scaleSetterGetter_scaleIsOutOfRang_getNormalizeValue() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN); - mWindowMagnificationManager.setScale(TEST_DISPLAY, 10.0f); + mMagnificationConnectionManager.setScale(TEST_DISPLAY, 10.0f); - assertEquals(mWindowMagnificationManager.getScale(TEST_DISPLAY), + assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY), MagnificationScaleProvider.MAX_SCALE); } @FlakyTest(bugId = 297879435) @Test public void logTrackingTypingFocus_processScroll_logDuration() { - WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager); - spyWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - spyWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, /* shown */ true); + MagnificationConnectionManager spyMagnificationConnectionManager = spy( + mMagnificationConnectionManager); + spyMagnificationConnectionManager.enableWindowMagnification( + TEST_DISPLAY, 3.0f, 50f, 50f); + spyMagnificationConnectionManager.onImeWindowVisibilityChanged( + TEST_DISPLAY, /* shown */ true); - spyWindowMagnificationManager.processScroll(TEST_DISPLAY, 10f, 10f); + spyMagnificationConnectionManager.processScroll(TEST_DISPLAY, 10f, 10f); - verify(spyWindowMagnificationManager).logTrackingTypingFocus(anyLong()); + verify(spyMagnificationConnectionManager).logTrackingTypingFocus(anyLong()); } @Test public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection(), never()) @@ -345,16 +347,16 @@ public class WindowMagnificationManagerTest { throws RemoteException { final float distanceX = 10f; final float distanceY = 10f; - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.processScroll(TEST_DISPLAY, distanceX, distanceY); + mMagnificationConnectionManager.processScroll(TEST_DISPLAY, distanceX, distanceY); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection(), never()) @@ -364,15 +366,15 @@ public class WindowMagnificationManagerTest { @Test public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.inset(-10, -10); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection(), never()) @@ -381,14 +383,14 @@ public class WindowMagnificationManagerTest { @Test public void onRectangleOnScreenRequested_imeVisibilityDefaultInvisible_withoutMovingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection(), never()) @@ -398,15 +400,15 @@ public class WindowMagnificationManagerTest { @Test public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), @@ -417,16 +419,16 @@ public class WindowMagnificationManagerTest { @Test public void onRectangleOnScreenRequested_imeInvisible_withoutMovingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection(), never()) @@ -436,17 +438,17 @@ public class WindowMagnificationManagerTest { @Test public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region outRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); final Rect requestedRect = outRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), @@ -456,58 +458,58 @@ public class WindowMagnificationManagerTest { @Test public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); final Region beforeRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); final Rect requestedRect = beforeRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.setMagnificationFollowTypingEnabled(false); + mMagnificationConnectionManager.setMagnificationFollowTypingEnabled(false); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); final Region afterRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion); assertEquals(afterRegion, beforeRegion); } @Test public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false); final Region beforeRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); final Rect requestedRect = beforeRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); final Region afterRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion); assertEquals(afterRegion, beforeRegion); } @Test public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); - mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationConnectionManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false); final Region beforeRegion = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); final Rect requestedRect = beforeRegion.getBounds(); requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); // Enabling a window magnifier again will turn on the tracking typing focus functionality. - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN); - mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY, requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY), @@ -517,43 +519,43 @@ public class WindowMagnificationManagerTest { @Test public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN); - mWindowMagnificationManager.moveWindowMagnification(TEST_DISPLAY, 200, 300); + mMagnificationConnectionManager.moveWindowMagnification(TEST_DISPLAY, 200, 300); verify(mMockConnection.getConnection()).moveWindowMagnifier(TEST_DISPLAY, 200, 300); } @Test public void showMagnificationButton_hasConnection_invokeConnectionMethod() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.showMagnificationButton(TEST_DISPLAY, + mMagnificationConnectionManager.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); verify(mMockConnection.getConnection()).showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - mWindowMagnificationManager.removeMagnificationButton(TEST_DISPLAY); + mMagnificationConnectionManager.removeMagnificationButton(TEST_DISPLAY); verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY); } @Test public void removeMagnificationSettingsPanel_hasConnection_invokeConnectionMethod() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.removeMagnificationSettingsPanel(TEST_DISPLAY); + mMagnificationConnectionManager.removeMagnificationSettingsPanel(TEST_DISPLAY); verify(mMockConnection.getConnection()).removeMagnificationSettingsPanel(TEST_DISPLAY); } @Test public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); final float testScale = 3f; - mWindowMagnificationManager.onUserMagnificationScaleChanged( + mMagnificationConnectionManager.onUserMagnificationScaleChanged( CURRENT_USER_ID, TEST_DISPLAY, testScale); verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged( eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale)); @@ -561,8 +563,8 @@ public class WindowMagnificationManagerTest { @Test public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(TEST_DISPLAY, new Rect(0, 0, 500, 500)); PointF[] pointersLocation = new PointF[2]; @@ -570,15 +572,15 @@ public class WindowMagnificationManagerTest { pointersLocation[1] = new PointF(300, 400); MotionEvent event = generatePointersDownEvent(pointersLocation); - assertEquals(mWindowMagnificationManager.pointersInWindow(TEST_DISPLAY, event), 1); + assertEquals(mMagnificationConnectionManager.pointersInWindow(TEST_DISPLAY, event), 1); } @Test public void onPerformScaleAction_magnifierEnabled_notifyAction() throws RemoteException { final float newScale = 4.0f; final boolean updatePersistence = true; - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); mMockConnection.getConnectionCallback().onPerformScaleAction( TEST_DISPLAY, newScale, updatePersistence); @@ -590,8 +592,8 @@ public class WindowMagnificationManagerTest { @Test public void onAccessibilityActionPerformed_magnifierEnabled_notifyAction() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); mMockConnection.getConnectionCallback().onAccessibilityActionPerformed(TEST_DISPLAY); @@ -600,22 +602,22 @@ public class WindowMagnificationManagerTest { @Test public void binderDied_windowMagnifierIsEnabled_resetState() throws RemoteException { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); mMockConnection.getDeathRecipient().binderDied(); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } @Test public void requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection() throws RemoteException { - assertTrue(mWindowMagnificationManager.requestConnection(true)); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + assertTrue(mMagnificationConnectionManager.requestConnection(true)); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); - assertTrue(mWindowMagnificationManager.requestConnection(false)); + assertTrue(mMagnificationConnectionManager.requestConnection(false)); verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null); verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(false); @@ -623,40 +625,40 @@ public class WindowMagnificationManagerTest { @Test public void requestConnection_requestWindowMagnificationConnection() throws RemoteException { - assertTrue(mWindowMagnificationManager.requestConnection(true)); + assertTrue(mMagnificationConnectionManager.requestConnection(true)); verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(true); } @Test public void isConnected_requestConnection_expectedValue() throws RemoteException { - mWindowMagnificationManager.requestConnection(true); - assertTrue(mWindowMagnificationManager.isConnected()); + mMagnificationConnectionManager.requestConnection(true); + assertTrue(mMagnificationConnectionManager.isConnected()); - mWindowMagnificationManager.requestConnection(false); - assertFalse(mWindowMagnificationManager.isConnected()); + mMagnificationConnectionManager.requestConnection(false); + assertFalse(mMagnificationConnectionManager.isConnected()); } @Test public void requestConnection_registerAndUnregisterBroadcastReceiver() { - assertTrue(mWindowMagnificationManager.requestConnection(true)); + assertTrue(mMagnificationConnectionManager.requestConnection(true)); verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); - assertTrue(mWindowMagnificationManager.requestConnection(false)); + assertTrue(mMagnificationConnectionManager.requestConnection(false)); verify(mContext).unregisterReceiver(any(BroadcastReceiver.class)); } @Test public void requestConnectionToNull_expectedGetterResults() { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1); - mWindowMagnificationManager.requestConnection(false); + mMagnificationConnectionManager.requestConnection(false); - assertEquals(1f, mWindowMagnificationManager.getScale(TEST_DISPLAY), 0); - assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterX(TEST_DISPLAY))); - assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterY(TEST_DISPLAY))); + assertEquals(1f, mMagnificationConnectionManager.getScale(TEST_DISPLAY), 0); + assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY))); + assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY))); final Region bounds = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds); assertTrue(bounds.isEmpty()); } @@ -664,9 +666,10 @@ public class WindowMagnificationManagerTest { public void enableWindowMagnification_connecting_invokeConnectionMethodAfterConnected() throws RemoteException { stubSetConnection(true); - mWindowMagnificationManager.requestConnection(true); + mMagnificationConnectionManager.requestConnection(true); - assertTrue(mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1)); + assertTrue(mMagnificationConnectionManager.enableWindowMagnification( + TEST_DISPLAY, 3f, 1, 1)); // Invoke enableWindowMagnification if the connection is connected. verify(mMockConnection.getConnection()).enableWindowMagnification( @@ -676,69 +679,73 @@ public class WindowMagnificationManagerTest { @Test public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, - 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY_2, 3f, - 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, null, + MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY_2, 3f, + 100f, 200f, null, + MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); - mWindowMagnificationManager.resetAllIfNeeded(SERVICE_ID); + mMagnificationConnectionManager.resetAllIfNeeded(SERVICE_ID); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); } @Test public void resetAllMagnification_enabledByDifferentId_windowMagnifierDisabled() { final int serviceId2 = SERVICE_ID + 1; - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, - 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY_2, 3f, - 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, serviceId2); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, null, + MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY_2, 3f, + 100f, 200f, null, + MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, serviceId2); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); - mWindowMagnificationManager.resetAllIfNeeded(SERVICE_ID); + mMagnificationConnectionManager.resetAllIfNeeded(SERVICE_ID); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); } @Test public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification() throws RemoteException { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN); - mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext, + mMagnificationConnectionManager.mScreenStateReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY); verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } @Test public void centerGetter_enabledOnTestDisplay_expectedValues() { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); - assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); - assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + assertEquals(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 200f); } @Test public void centerGetter_enabledOnTestDisplayWindowAtCenter_expectedValues() throws RemoteException { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, - 100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER); - assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); - assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + assertEquals(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 200f); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f), eq(100f), eq(200f), eq(0f), eq(0f), notNull()); @@ -747,12 +754,12 @@ public class WindowMagnificationManagerTest { @Test public void centerGetter_enabledOnTestDisplayWindowAtLeftTop_expectedValues() throws RemoteException { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, - 100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_TOP_LEFT); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, MagnificationConnectionManager.WINDOW_POSITION_AT_TOP_LEFT); - assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); - assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + assertEquals(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 200f); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f), eq(100f), eq(200f), eq(-1f), eq(-1f), notNull()); @@ -760,48 +767,48 @@ public class WindowMagnificationManagerTest { @Test public void magnifierGetters_disabled_expectedValues() { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, - 100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); - assertEquals(1f, mWindowMagnificationManager.getScale(TEST_DISPLAY), 0); - assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterX(TEST_DISPLAY))); - assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterY(TEST_DISPLAY))); + assertEquals(1f, mMagnificationConnectionManager.getScale(TEST_DISPLAY), 0); + assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY))); + assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY))); final Region bounds = new Region(); - mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds); + mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds); assertTrue(bounds.isEmpty()); } @Test public void onDisplayRemoved_enabledOnTestDisplay_disabled() { - mWindowMagnificationManager.requestConnection(true); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); + mMagnificationConnectionManager.requestConnection(true); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); - mWindowMagnificationManager.onDisplayRemoved(TEST_DISPLAY); + mMagnificationConnectionManager.onDisplayRemoved(TEST_DISPLAY); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } @Test public void onWindowMagnificationActivationState_magnifierEnabled_notifyActivatedState() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, true); } @Test public void onWindowMagnificationActivationState_magnifierDisabled_notifyDeactivatedState() { - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, false); Mockito.reset(mMockCallback); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); verify(mMockCallback, never()).onWindowMagnificationActivationState(eq(TEST_DISPLAY), anyBoolean()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java index cfd0289e5650..8f85f11b7c49 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java @@ -39,7 +39,7 @@ import org.mockito.MockitoAnnotations; /** * Tests for MagnificationConnectionWrapper. We don't test {@code * MagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in - * {@link WindowMagnificationManagerTest}. + * {@link MagnificationConnectionManagerTest}. */ public class MagnificationConnectionWrapperTest { @@ -73,9 +73,9 @@ public class MagnificationConnectionWrapperTest { } @Test - public void setScale() throws RemoteException { - mConnectionWrapper.setScale(TEST_DISPLAY, 3.0f); - verify(mConnection).setScale(TEST_DISPLAY, 3.0f); + public void setScaleForWindowMagnification() throws RemoteException { + mConnectionWrapper.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); + verify(mConnection).setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index d4c6fad99645..e8cdf35dee13 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -131,7 +131,7 @@ public class MagnificationControllerTest { private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor; private MockWindowMagnificationConnection mMockConnection; - private WindowMagnificationManager mWindowMagnificationManager; + private MagnificationConnectionManager mMagnificationConnectionManager; private MockContentResolver mMockResolver; private MagnificationController mMagnificationController; private final WindowMagnificationMgrCallbackDelegate @@ -205,13 +205,14 @@ public class MagnificationControllerTest { )); mScreenMagnificationController.register(TEST_DISPLAY); - mWindowMagnificationManager = spy(new WindowMagnificationManager(mContext, globalLock, - mWindowMagnificationCallbackDelegate, mTraceManager, mScaleProvider)); + mMagnificationConnectionManager = spy( + new MagnificationConnectionManager(mContext, globalLock, + mWindowMagnificationCallbackDelegate, mTraceManager, mScaleProvider)); mMockConnection = new MockWindowMagnificationConnection(true); - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext, - mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider, + mScreenMagnificationController, mMagnificationConnectionManager, mScaleProvider, ConcurrentUtils.DIRECT_EXECUTOR)); mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); @@ -254,8 +255,10 @@ public class MagnificationControllerTest { mCallbackArgumentCaptor.getValue().onResult(true); mMockConnection.invokeCallbacks(); verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); - assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); - assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_X, + mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, + mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 0); } @Test @@ -297,8 +300,10 @@ public class MagnificationControllerTest { mMockConnection.invokeCallbacks(); verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); - assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); - assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_X, + mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, + mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 0); } @Test @@ -316,7 +321,7 @@ public class MagnificationControllerTest { // The first time is triggered when window mode is activated. // The second time is triggered when activating the window mode again. // The third time is triggered when the transition is completed. - verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); } @@ -330,7 +335,7 @@ public class MagnificationControllerTest { mTransitionCallBack); mMockConnection.invokeCallbacks(); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, MAGNIFICATION_GESTURE_HANDLER_ID); @@ -350,7 +355,7 @@ public class MagnificationControllerTest { mTransitionCallBack); mMockConnection.invokeCallbacks(); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, magnificationBounds.exactCenterX(), magnificationBounds.exactCenterY(), true, MAGNIFICATION_GESTURE_HANDLER_ID); @@ -386,11 +391,11 @@ public class MagnificationControllerTest { mTransitionCallBack); // Enable window magnification while animating. - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, Float.NaN, Float.NaN, null, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); - assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, MAGNIFICATION_GESTURE_HANDLER_ID); @@ -408,8 +413,10 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false)); mMockConnection.invokeCallbacks(); - assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); - assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_X, + mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, + mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 0); } @Test @@ -438,7 +445,7 @@ public class MagnificationControllerTest { animate, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), eq(DEFAULT_SCALE), eq(MAGNIFIED_CENTER_X), eq(MAGNIFIED_CENTER_Y), any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID)); @@ -564,7 +571,7 @@ public class MagnificationControllerTest { mMagnificationController.onDisplayRemoved(TEST_DISPLAY); verify(mScreenMagnificationController).onDisplayRemoved(TEST_DISPLAY); - verify(mWindowMagnificationManager).onDisplayRemoved(TEST_DISPLAY); + verify(mMagnificationConnectionManager).onDisplayRemoved(TEST_DISPLAY); verify(mScaleProvider).onDisplayRemoved(TEST_DISPLAY); } @@ -573,7 +580,7 @@ public class MagnificationControllerTest { mMagnificationController.updateUserIdIfNeeded(SECOND_USER_ID); verify(mScreenMagnificationController).resetAllIfNeeded(false); - verify(mWindowMagnificationManager).disableAllWindowMagnifiers(); + verify(mMagnificationConnectionManager).disableAllWindowMagnifiers(); verify(mScaleProvider).onUserChanged(SECOND_USER_ID); } @@ -584,7 +591,7 @@ public class MagnificationControllerTest { mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); - assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } @Test @@ -594,11 +601,11 @@ public class MagnificationControllerTest { // The first time is trigger when fullscreen mode is activated. // The second time is triggered when magnification spec is changed. - verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -625,8 +632,8 @@ public class MagnificationControllerTest { mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence); - verify(mWindowMagnificationManager).setScale(eq(TEST_DISPLAY), eq(newScale)); - verify(mWindowMagnificationManager, never()).persistScale(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY), eq(newScale)); + verify(mMagnificationConnectionManager, never()).persistScale(eq(TEST_DISPLAY)); } @Test @@ -669,7 +676,7 @@ public class MagnificationControllerTest { assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0); assertEquals(config.getScale(), actualConfig.getScale(), 0); - verify(mWindowMagnificationManager).onUserMagnificationScaleChanged( + verify(mMagnificationConnectionManager).onUserMagnificationScaleChanged( /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale())); } @@ -677,11 +684,11 @@ public class MagnificationControllerTest { public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - reset(mWindowMagnificationManager); + reset(mMagnificationConnectionManager); mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT); - verify(mWindowMagnificationManager).onUserMagnificationScaleChanged( + verify(mMagnificationConnectionManager).onUserMagnificationScaleChanged( /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE)); } @@ -780,11 +787,11 @@ public class MagnificationControllerTest { // The first time is triggered when window mode is activated. // The second time is triggered when accessibility action performed. - verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -799,10 +806,11 @@ public class MagnificationControllerTest { // The first time is triggered when window mode is activated. // The second time is triggered when accessibility action performed. - verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager, times(2)) + .removeMagnificationButton(eq(TEST_DISPLAY)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -816,7 +824,7 @@ public class MagnificationControllerTest { public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true); verify(mMagnificationController).onWindowMagnificationActivationState( eq(TEST_DISPLAY), eq(false)); @@ -828,7 +836,7 @@ public class MagnificationControllerTest { public void setPreferenceMagnificationFollowTypingEnabled_setPrefDisabled_disableAll() { mMagnificationController.setMagnificationFollowTypingEnabled(false); - verify(mWindowMagnificationManager).setMagnificationFollowTypingEnabled(eq(false)); + verify(mMagnificationConnectionManager).setMagnificationFollowTypingEnabled(eq(false)); verify(mScreenMagnificationController).setMagnificationFollowTypingEnabled(eq(false)); } @@ -850,7 +858,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController).onRectangleOnScreenRequested(eq(TEST_DISPLAY), eq(TEST_RECT.left), eq(TEST_RECT.top), eq(TEST_RECT.right), eq(TEST_RECT.bottom)); - verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(), + verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); } @@ -867,7 +875,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested(anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); - verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(), + verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); } @@ -880,7 +888,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested( eq(TEST_DISPLAY), anyInt(), anyInt(), anyInt(), anyInt()); - verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(), + verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); } @@ -895,7 +903,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested( eq(TEST_DISPLAY), anyInt(), anyInt(), anyInt(), anyInt()); - verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(), + verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); } @@ -970,7 +978,8 @@ public class MagnificationControllerTest { mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); - verify(mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY), eq(false)); + verify(mMagnificationConnectionManager) + .disableWindowMagnification(eq(TEST_DISPLAY), eq(false)); } @Test @@ -983,11 +992,11 @@ public class MagnificationControllerTest { // The first time is triggered when fullscreen mode is activated. // The second time is triggered when magnification spec is changed. // The third time is triggered when user interaction changed. - verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1001,11 +1010,11 @@ public class MagnificationControllerTest { // The first time is triggered when fullscreen mode is activated. // The second time is triggered when magnification spec is changed. // The third time is triggered when user interaction changed. - verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1018,11 +1027,11 @@ public class MagnificationControllerTest { // The first time is triggered when the window mode is activated. // The second time is triggered when user interaction changed. - verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1035,11 +1044,11 @@ public class MagnificationControllerTest { // The first time is triggered when the window mode is activated. // The second time is triggered when user interaction changed. - verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1053,11 +1062,11 @@ public class MagnificationControllerTest { mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN); mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN); - verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, never()).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); // The first time is triggered when fullscreen mode is activated. // The second time is triggered when magnification spec is changed. - verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, times(2)).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1071,9 +1080,9 @@ public class MagnificationControllerTest { mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN); mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN); - verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, never()).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); - verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, times(2)).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1082,11 +1091,11 @@ public class MagnificationControllerTest { throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1100,11 +1109,11 @@ public class MagnificationControllerTest { // The first time is triggered when fullscreen mode is activated. // The second time is triggered when magnification spec is changed. // The third time is triggered when fullscreen mode activation state is updated. - verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel // in current capability and mode, and the magnification is activated. - verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel( eq(TEST_DISPLAY)); } @@ -1113,10 +1122,10 @@ public class MagnificationControllerTest { throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); - verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); - verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationButton(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1126,8 +1135,8 @@ public class MagnificationControllerTest { setMagnificationEnabled(MODE_FULLSCREEN); mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ true); - verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); - verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationButton(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1142,10 +1151,10 @@ public class MagnificationControllerTest { // The first time is triggered when fullscreen mode is activated. // The second time is triggered when magnification spec is changed. // The third time is triggered when the disable-magnification callback is triggered. - verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); // It is triggered when the disable-magnification callback is triggered. - verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1163,10 +1172,10 @@ public class MagnificationControllerTest { // The first time is triggered when window mode is activated. // The second time is triggered when the disable-magnification callback is triggered. - verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), + verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); // It is triggered when the disable-magnification callback is triggered. - verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1174,9 +1183,9 @@ public class MagnificationControllerTest { throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false); - verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1185,7 +1194,7 @@ public class MagnificationControllerTest { setMagnificationEnabled(MODE_FULLSCREEN); mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ true); - verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); + verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1260,17 +1269,17 @@ public class MagnificationControllerTest { private void activateMagnifier(int displayId, int mode, float centerX, float centerY) throws RemoteException { - final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled( + final boolean windowMagnifying = mMagnificationConnectionManager.isWindowMagnifierEnabled( displayId); if (windowMagnifying) { - mWindowMagnificationManager.disableWindowMagnification(displayId, false); + mMagnificationConnectionManager.disableWindowMagnification(displayId, false); mMockConnection.invokeCallbacks(); } if (mode == MODE_FULLSCREEN) { mScreenMagnificationController.setScaleAndCenter(displayId, DEFAULT_SCALE, centerX, centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } else { - mWindowMagnificationManager.enableWindowMagnification(displayId, DEFAULT_SCALE, + mMagnificationConnectionManager.enableWindowMagnification(displayId, DEFAULT_SCALE, centerX, centerY, null, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); } @@ -1304,10 +1313,10 @@ public class MagnificationControllerTest { } private static class WindowMagnificationMgrCallbackDelegate implements - WindowMagnificationManager.Callback { - private WindowMagnificationManager.Callback mCallback; + MagnificationConnectionManager.Callback { + private MagnificationConnectionManager.Callback mCallback; - public void setDelegate(WindowMagnificationManager.Callback callback) { + public void setDelegate(MagnificationConnectionManager.Callback callback) { mCallback = callback; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index 612a091a6b1b..c4be51f9ecbd 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -92,7 +92,7 @@ public class WindowMagnificationGestureHandlerTest { public final TestableContext mContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getContext()); - private WindowMagnificationManager mWindowMagnificationManager; + private MagnificationConnectionManager mMagnificationConnectionManager; private MockWindowMagnificationConnection mMockConnection; private SpyWindowMagnificationGestureHandler mWindowMagnificationGestureHandler; private WindowMagnificationGestureHandler mMockWindowMagnificationGestureHandler; @@ -104,23 +104,23 @@ public class WindowMagnificationGestureHandlerTest { @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mWindowMagnificationManager = new WindowMagnificationManager(mContext, new Object(), - mock(WindowMagnificationManager.Callback.class), mMockTrace, + mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(), + mock(MagnificationConnectionManager.Callback.class), mMockTrace, new MagnificationScaleProvider(mContext)); mMockConnection = new MockWindowMagnificationConnection(); mWindowMagnificationGestureHandler = new SpyWindowMagnificationGestureHandler( - mContext, mWindowMagnificationManager, mMockTrace, mMockCallback, + mContext, mMagnificationConnectionManager, mMockTrace, mMockCallback, /** detectSingleFingerTripleTap= */ true, /** detectTwoFingerTripleTap= */ true, /** detectShortcutTrigger= */ true, DISPLAY_0); mMockWindowMagnificationGestureHandler = mWindowMagnificationGestureHandler.getMockGestureHandler(); - mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class)); } @After public void tearDown() { - mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, true); + mMagnificationConnectionManager.disableWindowMagnification(DISPLAY_0, true); } @Test @@ -378,7 +378,7 @@ public class WindowMagnificationGestureHandlerTest { } break; case STATE_SHOW_MAGNIFIER_SHORTCUT: { - mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, false); + mMagnificationConnectionManager.disableWindowMagnification(DISPLAY_0, false); } break; case STATE_TWO_FINGERS_DOWN: { @@ -423,7 +423,7 @@ public class WindowMagnificationGestureHandlerTest { } private boolean isWindowMagnifierEnabled(int displayId) { - return mWindowMagnificationManager.isWindowMagnifierEnabled(displayId); + return mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId); } private static String stateToString(int state) { @@ -495,13 +495,14 @@ public class WindowMagnificationGestureHandlerTest { private final WindowMagnificationGestureHandler mMockWindowMagnificationGestureHandler; SpyWindowMagnificationGestureHandler(@UiContext Context context, - WindowMagnificationManager windowMagnificationMgr, + MagnificationConnectionManager magnificationConnectionManager, AccessibilityTraceManager trace, Callback callback, boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger, int displayId) { - super(context, windowMagnificationMgr, trace, callback, detectSingleFingerTripleTap, - detectTwoFingerTripleTap, detectShortcutTrigger, displayId); + super(context, magnificationConnectionManager, trace, callback, + detectSingleFingerTripleTap, detectTwoFingerTripleTap, + detectShortcutTrigger, displayId); mMockWindowMagnificationGestureHandler = mock(WindowMagnificationGestureHandler.class); } diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java index 749b07d16ebe..9c8276aac4dd 100644 --- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java @@ -19,6 +19,16 @@ import static android.media.AudioManager.GET_DEVICES_OUTPUTS; import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; +import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; +import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; +import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; + +import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_LARGE; +import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_MEDIUM; +import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_SMALL; +import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_UNKNOWN; + +import static junit.framework.Assert.assertEquals; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; @@ -34,11 +44,15 @@ import android.media.ILoudnessCodecUpdatesDispatcher; import android.media.LoudnessCodecInfo; import android.media.PlayerBase; import android.os.IBinder; +import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.server.audio.LoudnessCodecHelper.DeviceSplRange; +import com.android.server.audio.LoudnessCodecHelper.LoudnessCodecInputProperties; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -84,8 +98,7 @@ public class LoudnessCodecHelperTest { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_4))); + List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); } @@ -96,8 +109,7 @@ public class LoudnessCodecHelperTest { mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/false, - CODEC_METADATA_TYPE_MPEG_D))); + List.of(getLoudnessInfo(/*isDownmixing=*/false, CODEC_METADATA_TYPE_MPEG_D))); verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); @@ -108,11 +120,9 @@ public class LoudnessCodecHelperTest { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_4))); - mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, - getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_D)); + List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); + mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222, + getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); @@ -124,11 +134,10 @@ public class LoudnessCodecHelperTest { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, + List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); - mLoudnessHelper.addLoudnessCodecInfo(newPiid, - getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_D)); + mLoudnessHelper.addLoudnessCodecInfo(newPiid, /*mediaCodecHash=*/222, + getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); @@ -140,12 +149,10 @@ public class LoudnessCodecHelperTest { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, - List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_4))); + List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4))); //does not trigger dispatch since active apc list does not contain newPiid mLoudnessHelper.startLoudnessCodecUpdates(newPiid, - List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_D))); + List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D))); verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); @@ -157,9 +164,8 @@ public class LoudnessCodecHelperTest { @Test public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception { mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); - mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, - getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, - CODEC_METADATA_TYPE_MPEG_D)); + mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222, + getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)); mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); @@ -170,8 +176,8 @@ public class LoudnessCodecHelperTest { @Test public void updateCodecParameters_removedCodecInfo_noDispatch() throws Exception { - final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111, - /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4); + final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4); mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info)); @@ -186,8 +192,8 @@ public class LoudnessCodecHelperTest { @Test public void updateCodecParameters_stoppedPiids_noDispatch() throws Exception { - final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111, - /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4); + final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4); mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info)); @@ -200,6 +206,108 @@ public class LoudnessCodecHelperTest { any()); } + @Test + public void checkParcelableBundle_forMpeg4CodecInputProperties() { + PersistableBundle loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true, + SPL_RANGE_SMALL).createLoudnessParameters(); + assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false, + SPL_RANGE_SMALL).createLoudnessParameters(); + assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true, + SPL_RANGE_MEDIUM).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false, + SPL_RANGE_MEDIUM).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true, + SPL_RANGE_LARGE).createLoudnessParameters(); + assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false, + SPL_RANGE_LARGE).createLoudnessParameters(); + assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true, + SPL_RANGE_UNKNOWN).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false, + SPL_RANGE_UNKNOWN).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); + } + + @Test + public void checkParcelableBundle_forMpegDCodecInputProperties() { + PersistableBundle loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true, + SPL_RANGE_SMALL).createLoudnessParameters(); + assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(3, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false, + SPL_RANGE_SMALL).createLoudnessParameters(); + assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(3, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true, + SPL_RANGE_MEDIUM).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false, + SPL_RANGE_MEDIUM).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true, + SPL_RANGE_LARGE).createLoudnessParameters(); + assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false, + SPL_RANGE_LARGE).createLoudnessParameters(); + assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true, + SPL_RANGE_UNKNOWN).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + + loudnessParameters = createInputProperties( + CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false, + SPL_RANGE_UNKNOWN).createLoudnessParameters(); + assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + } + private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) { final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>(); @@ -220,11 +328,15 @@ public class LoudnessCodecHelperTest { return apcList; } - private static LoudnessCodecInfo getLoudnessInfo(int mediaCodecHash, boolean isDownmixing, - int metadataType) { + private static LoudnessCodecInputProperties createInputProperties( + int metadataType, boolean isDownmixing, @DeviceSplRange int splRange) { + return new LoudnessCodecInputProperties.Builder().setMetadataType( + metadataType).setIsDownmixing(isDownmixing).setDeviceSplRange(splRange).build(); + } + + private static LoudnessCodecInfo getLoudnessInfo(boolean isDownmixing, int metadataType) { LoudnessCodecInfo info = new LoudnessCodecInfo(); info.isDownmixing = isDownmixing; - info.mediaCodecHashCode = mediaCodecHash; info.metadataType = metadataType; return info; diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index b5ba322b1a5e..9213601a6144 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -413,18 +413,24 @@ public class VirtualDeviceManagerServiceTest { public void getDeviceIdForDisplayId_invalidDisplayId_returnsDefault() { assertThat(mVdm.getDeviceIdForDisplayId(Display.INVALID_DISPLAY)) .isEqualTo(DEVICE_ID_DEFAULT); + assertThat(mLocalService.getDeviceIdForDisplayId(Display.INVALID_DISPLAY)) + .isEqualTo(DEVICE_ID_DEFAULT); } @Test public void getDeviceIdForDisplayId_defaultDisplayId_returnsDefault() { assertThat(mVdm.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY)) .isEqualTo(DEVICE_ID_DEFAULT); + assertThat(mLocalService.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY)) + .isEqualTo(DEVICE_ID_DEFAULT); } @Test public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() { assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID)) .isEqualTo(DEVICE_ID_DEFAULT); + assertThat(mLocalService.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID)) + .isEqualTo(DEVICE_ID_DEFAULT); } @Test @@ -433,6 +439,8 @@ public class VirtualDeviceManagerServiceTest { assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1)) .isEqualTo(mDeviceImpl.getDeviceId()); + assertThat(mLocalService.getDeviceIdForDisplayId(DISPLAY_ID_1)) + .isEqualTo(mDeviceImpl.getDeviceId()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java index 01922e08d71d..edfe1b416f22 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java @@ -40,6 +40,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Surface; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,6 +78,11 @@ public class VirtualCameraControllerTest { when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true); } + @After + public void tearDown() throws Exception { + mVirtualCameraController.close(); + } + @Test public void registerCamera_registersCamera() throws Exception { mVirtualCameraController.registerCamera(createVirtualCameraConfig( @@ -95,6 +101,8 @@ public class VirtualCameraControllerTest { public void unregisterCamera_unregistersCamera() throws Exception { VirtualCameraConfig config = createVirtualCameraConfig( CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1); + mVirtualCameraController.registerCamera(config); + mVirtualCameraController.unregisterCamera(config); verify(mVirtualCameraServiceMock).unregisterCamera(any()); @@ -107,9 +115,10 @@ public class VirtualCameraControllerTest { mVirtualCameraController.registerCamera(createVirtualCameraConfig( CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2)); + mVirtualCameraController.close(); + ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = ArgumentCaptor.forClass(VirtualCameraConfiguration.class); - mVirtualCameraController.close(); verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(), configurationCaptor.capture()); List<VirtualCameraConfiguration> virtualCameraConfigurations = diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java deleted file mode 100644 index 5aef7a320930..000000000000 --- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2023 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.media; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.res.Resources; -import android.media.AudioManager; -import android.media.AudioRoutesInfo; -import android.media.IAudioRoutesObserver; -import android.media.MediaRoute2Info; -import android.os.RemoteException; - -import com.android.internal.R; -import com.android.server.audio.AudioService; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(JUnit4.class) -public class AudioPoliciesDeviceRouteControllerTest { - - private static final String ROUTE_NAME_DEFAULT = "default"; - private static final String ROUTE_NAME_DOCK = "dock"; - private static final String ROUTE_NAME_HEADPHONES = "headphones"; - - private static final int VOLUME_SAMPLE_1 = 25; - - @Mock - private Context mContext; - @Mock - private Resources mResources; - @Mock - private AudioManager mAudioManager; - @Mock - private AudioService mAudioService; - @Mock - private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; - - @Captor - private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor; - - private AudioPoliciesDeviceRouteController mController; - - private IAudioRoutesObserver.Stub mAudioRoutesObserver; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - when(mContext.getResources()).thenReturn(mResources); - when(mResources.getText(anyInt())).thenReturn(ROUTE_NAME_DEFAULT); - - // Setting built-in speaker as default speaker. - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER; - when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture())) - .thenReturn(audioRoutesInfo); - - mController = new AudioPoliciesDeviceRouteController( - mContext, mAudioManager, mAudioService, mOnDeviceRouteChangedListener); - - mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue(); - } - - @Test - public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() { - MediaRoute2Info route2Info = mController.getSelectedRoute(); - - assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); - } - - @Test - public void getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService() { - when(mResources.getText(R.string.default_audio_route_name_headphones)) - .thenReturn(ROUTE_NAME_HEADPHONES); - - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; - callAudioRoutesObserver(audioRoutesInfo); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); - } - - @Test - public void getDeviceRoute_selectDevice_returnsSelectedRoute() { - when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) - .thenReturn(ROUTE_NAME_DOCK); - - mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); - } - - @Test - public void getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute() { - when(mResources.getText(R.string.default_audio_route_name_headphones)) - .thenReturn(ROUTE_NAME_HEADPHONES); - when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) - .thenReturn(ROUTE_NAME_DOCK); - - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; - callAudioRoutesObserver(audioRoutesInfo); - - mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); - } - - @Test - public void getDeviceRoute_unselectRoute_returnsAudioServiceRoute() { - when(mResources.getText(R.string.default_audio_route_name_headphones)) - .thenReturn(ROUTE_NAME_HEADPHONES); - when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) - .thenReturn(ROUTE_NAME_DOCK); - - mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; - callAudioRoutesObserver(audioRoutesInfo); - - mController.selectRoute(null); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); - } - - @Test - public void getDeviceRoute_selectRouteFails_returnsAudioServiceRoute() { - when(mResources.getText(R.string.default_audio_route_name_headphones)) - .thenReturn(ROUTE_NAME_HEADPHONES); - - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; - callAudioRoutesObserver(audioRoutesInfo); - - mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); - } - - @Test - public void selectRoute_selectWiredRoute_returnsTrue() { - assertThat(mController.selectRoute(MediaRoute2Info.TYPE_HDMI)).isTrue(); - } - - @Test - public void selectRoute_selectBluetoothRoute_returnsFalse() { - assertThat(mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)).isFalse(); - } - - @Test - public void selectRoute_unselectRoute_returnsTrue() { - assertThat(mController.selectRoute(null)).isTrue(); - } - - @Test - public void updateVolume_noSelectedRoute_deviceRouteVolumeChanged() { - when(mResources.getText(R.string.default_audio_route_name_headphones)) - .thenReturn(ROUTE_NAME_HEADPHONES); - - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; - callAudioRoutesObserver(audioRoutesInfo); - - mController.updateVolume(VOLUME_SAMPLE_1); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); - assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); - } - - @Test - public void updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged() { - when(mResources.getText(R.string.default_audio_route_name_headphones)) - .thenReturn(ROUTE_NAME_HEADPHONES); - when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) - .thenReturn(ROUTE_NAME_DOCK); - - AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); - audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; - callAudioRoutesObserver(audioRoutesInfo); - - mController.updateVolume(VOLUME_SAMPLE_1); - - mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - - MediaRoute2Info route2Info = mController.getSelectedRoute(); - assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); - assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); - } - - /** - * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)} - * from {@link AudioService}. This happens when there is a wired route change, - * like a wired headset being connected. - * - * @param audioRoutesInfo updated state of connected wired device - */ - private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) { - try { - // this is a captured observer implementation - // from WiredRoutesController's AudioService#startWatchingRoutes call - mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo); - } catch (RemoteException exception) { - // Should not happen since the object is mocked. - assertWithMessage("An unexpected RemoteException happened.").fail(); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java index 14b121d3945c..0961b7d97177 100644 --- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.media; import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; import android.content.Context; +import android.os.Looper; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -56,7 +57,8 @@ public class DeviceRouteControllerTest { @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() { DeviceRouteController deviceRouteController = - DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener); + DeviceRouteController.createInstance( + mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener); Truth.assertThat(deviceRouteController).isInstanceOf(LegacyDeviceRouteController.class); } @@ -65,7 +67,8 @@ public class DeviceRouteControllerTest { @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() { DeviceRouteController deviceRouteController = - DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener); + DeviceRouteController.createInstance( + mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener); Truth.assertThat(deviceRouteController) .isInstanceOf(AudioPoliciesDeviceRouteController.class); diff --git a/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java b/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java new file mode 100644 index 000000000000..377e4c3d6810 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2023 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.utils; + +import static org.junit.Assert.assertEquals; + +import android.provider.DeviceConfig; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link UserSettingDeviceConfigMediator} + */ +@RunWith(AndroidJUnit4.class) +public class UserSettingDeviceConfigMediatorTest { + @Test + public void testDeviceConfigOnly() { + UserSettingDeviceConfigMediator mediator = + new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(','); + + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test") + .setInt("int", 1) + .setFloat("float", .5f) + .setBoolean("boolean", true) + .setLong("long", 123456789) + .setString("string", "abc123") + .build(); + + mediator.setDeviceConfigProperties(properties); + + assertEquals(1, mediator.getInt("int", 123)); + assertEquals(123, mediator.getInt("invalidKey", 123)); + assertEquals(.5f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001); + assertEquals(true, mediator.getBoolean("boolean", false)); + assertEquals(true, mediator.getBoolean("invalidKey", true)); + assertEquals(123456789, mediator.getLong("long", 987654321)); + assertEquals(987654321, mediator.getInt("invalidKey", 987654321)); + assertEquals("abc123", mediator.getString("string", "xyz987")); + assertEquals("xyz987", mediator.getString("invalidKey", "xyz987")); + + // Clear the properties + mediator.setDeviceConfigProperties(null); + + assertEquals(123, mediator.getInt("int", 123)); + assertEquals(123, mediator.getInt("invalidKey", 123)); + assertEquals(.8f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001); + assertEquals(false, mediator.getBoolean("boolean", false)); + assertEquals(true, mediator.getBoolean("invalidKey", true)); + assertEquals(987654321, mediator.getLong("long", 987654321)); + assertEquals(987654321, mediator.getInt("invalidKey", 987654321)); + assertEquals("xyz987", mediator.getString("string", "xyz987")); + assertEquals("xyz987", mediator.getString("invalidKey", "xyz987")); + } + + @Test + public void testSettingsOnly() { + UserSettingDeviceConfigMediator mediator = + new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(','); + + String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123"; + + mediator.setSettingsString(settings); + + assertEquals(1, mediator.getInt("int", 123)); + assertEquals(123, mediator.getInt("invalidKey", 123)); + assertEquals(.5f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001); + assertEquals(true, mediator.getBoolean("boolean", false)); + assertEquals(true, mediator.getBoolean("invalidKey", true)); + assertEquals(123456789, mediator.getLong("long", 987654321)); + assertEquals(987654321, mediator.getInt("invalidKey", 987654321)); + assertEquals("abc123", mediator.getString("string", "xyz987")); + assertEquals("xyz987", mediator.getString("invalidKey", "xyz987")); + + // Clear the settings + mediator.setSettingsString(null); + + assertEquals(123, mediator.getInt("int", 123)); + assertEquals(123, mediator.getInt("invalidKey", 123)); + assertEquals(.8f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001); + assertEquals(false, mediator.getBoolean("boolean", false)); + assertEquals(true, mediator.getBoolean("invalidKey", true)); + assertEquals(987654321, mediator.getLong("long", 987654321)); + assertEquals(987654321, mediator.getInt("invalidKey", 987654321)); + assertEquals("xyz987", mediator.getString("string", "xyz987")); + assertEquals("xyz987", mediator.getString("invalidKey", "xyz987")); + } + + @Test + public void testSettingsOverridesAll() { + UserSettingDeviceConfigMediator mediator = + new UserSettingDeviceConfigMediator.SettingsOverridesAllMediator(','); + + String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123," + + "intOnlyInSettings=9,floatOnlyInSettings=.25f,booleanOnlyInSettings=true," + + "longOnlyInSettings=53771465,stringOnlyInSettings=settingsString"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test") + .setInt("int", 10) + .setInt("intOnlyInDeviceConfig", 9001) + .setFloat("float", .7f) + .setFloat("floatOnlyInDeviceConfig", .9f) + .setBoolean("boolean", false) + .setBoolean("booleanOnlyInDeviceConfig", true) + .setLong("long", 60000001) + .setLong("longOnlyInDeviceConfig", 7357) + .setString("string", "xyz987") + .setString("stringOnlyInDeviceConfig", "deviceConfigString") + .build(); + + mediator.setSettingsString(settings); + mediator.setDeviceConfigProperties(properties); + + // Since settings overrides all, anything in DeviceConfig should be ignored, + // even if settings doesn't have a value for it. + assertEquals(1, mediator.getInt("int", 123)); + assertEquals(9, mediator.getInt("intOnlyInSettings", 123)); + assertEquals(123, mediator.getInt("intOnlyInDeviceConfig", 123)); + assertEquals(.5f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.25f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001); + assertEquals(true, mediator.getBoolean("boolean", false)); + assertEquals(true, mediator.getBoolean("booleanOnlyInSettings", false)); + assertEquals(false, mediator.getBoolean("booleanOnlyInDeviceConfig", false)); + assertEquals(123456789, mediator.getLong("long", 987654321)); + assertEquals(53771465, mediator.getLong("longOnlyInSettings", 987654321)); + assertEquals(987654321, mediator.getLong("longOnlyInDeviceConfig", 987654321)); + assertEquals("abc123", mediator.getString("string", "default")); + assertEquals("settingsString", mediator.getString("stringOnlyInSettings", "default")); + assertEquals("default", mediator.getString("stringOnlyInDeviceConfig", "default")); + + // Nothing in settings, do DeviceConfig can be used. + mediator.setSettingsString(""); + + assertEquals(10, mediator.getInt("int", 123)); + assertEquals(123, mediator.getInt("intOnlyInSettings", 123)); + assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123)); + assertEquals(.7f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001); + assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001); + assertEquals(false, mediator.getBoolean("boolean", false)); + assertEquals(false, mediator.getBoolean("booleanOnlyInSettings", false)); + assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false)); + assertEquals(60000001, mediator.getLong("long", 987654321)); + assertEquals(987654321, mediator.getLong("longOnlyInSettings", 987654321)); + assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321)); + assertEquals("xyz987", mediator.getString("string", "default")); + assertEquals("default", mediator.getString("stringOnlyInSettings", "default")); + assertEquals("deviceConfigString", + mediator.getString("stringOnlyInDeviceConfig", "default")); + + // Nothing in settings, do DeviceConfig can be used. + mediator.setSettingsString(null); + + assertEquals(10, mediator.getInt("int", 123)); + assertEquals(123, mediator.getInt("intOnlyInSettings", 123)); + assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123)); + assertEquals(.7f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.8f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001); + assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001); + assertEquals(false, mediator.getBoolean("boolean", false)); + assertEquals(false, mediator.getBoolean("booleanOnlyInSettings", false)); + assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false)); + assertEquals(60000001, mediator.getLong("long", 987654321)); + assertEquals(987654321, mediator.getLong("longOnlyInSettings", 987654321)); + assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321)); + assertEquals("xyz987", mediator.getString("string", "default")); + assertEquals("default", mediator.getString("stringOnlyInSettings", "default")); + assertEquals("deviceConfigString", + mediator.getString("stringOnlyInDeviceConfig", "default")); + } + + @Test + public void testSettingsOverridesIndividual() { + UserSettingDeviceConfigMediator mediator = + new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(','); + + String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123," + + "intOnlyInSettings=9,floatOnlyInSettings=.25f,booleanOnlyInSettings=true," + + "longOnlyInSettings=53771465,stringOnlyInSettings=settingsString"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test") + .setInt("int", 10) + .setInt("intOnlyInDeviceConfig", 9001) + .setFloat("float", .7f) + .setFloat("floatOnlyInDeviceConfig", .9f) + .setBoolean("boolean", false) + .setBoolean("booleanOnlyInDeviceConfig", true) + .setLong("long", 60000001) + .setLong("longOnlyInDeviceConfig", 7357) + .setString("string", "xyz987") + .setString("stringOnlyInDeviceConfig", "deviceConfigString") + .build(); + + mediator.setSettingsString(settings); + mediator.setDeviceConfigProperties(properties); + + // Since settings overrides individual, anything in DeviceConfig that doesn't exist in + // settings should be used. + assertEquals(1, mediator.getInt("int", 123)); + assertEquals(9, mediator.getInt("intOnlyInSettings", 123)); + assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123)); + assertEquals(.5f, mediator.getFloat("float", .8f), 0.001); + assertEquals(.25f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001); + assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001); + assertEquals(true, mediator.getBoolean("boolean", false)); + assertEquals(true, mediator.getBoolean("booleanOnlyInSettings", false)); + assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false)); + assertEquals(123456789, mediator.getLong("long", 987654321)); + assertEquals(53771465, mediator.getLong("longOnlyInSettings", 987654321)); + assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321)); + assertEquals("abc123", mediator.getString("string", "default")); + assertEquals("settingsString", mediator.getString("stringOnlyInSettings", "default")); + assertEquals("deviceConfigString", + mediator.getString("stringOnlyInDeviceConfig", "default")); + } +} diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java index d00060564e74..32082e3d857e 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -28,6 +28,7 @@ import android.content.pm.Signature; import android.os.Build; import android.os.Bundle; import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.suitebuilder.annotation.MediumTest; @@ -1449,4 +1450,21 @@ public class WebViewUpdateServiceTest { checkPreparationPhasesForPackage(currentSdkPackage.packageName, 1 /* first preparation phase */); } + + @Test + @RequiresFlagsEnabled("android.webkit.update_service_v2") + public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() { + String nonDefaultPackage = "nonDefaultPackage"; + String defaultPackage1 = "defaultPackage1"; + String defaultPackage2 = "defaultPackage2"; + WebViewProviderInfo[] packages = + new WebViewProviderInfo[] { + new WebViewProviderInfo(nonDefaultPackage, "", false, false, null), + new WebViewProviderInfo(defaultPackage1, "", true, false, null), + new WebViewProviderInfo(defaultPackage2, "", true, false, null) + }; + setupWithPackages(packages); + assertEquals( + defaultPackage1, mWebViewUpdateServiceImpl.getDefaultWebViewPackage().packageName); + } } 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 09ffe71a6758..ee08fd26d631 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -28,6 +28,7 @@ import static android.app.Notification.FLAG_AUTO_CANCEL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; +import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; @@ -320,6 +321,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final int SECONDARY_DISPLAY_ID = 42; private static final int TEST_PROFILE_USERHANDLE = 12; + private static final String ACTION_NOTIFICATION_TIMEOUT = + NotificationManagerService.class.getSimpleName() + ".TIMEOUT"; + private static final String EXTRA_KEY = "key"; + private static final String SCHEME_TIMEOUT = "timeout"; + private final int mUid = Binder.getCallingUid(); private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); @@ -442,6 +448,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; BroadcastReceiver mUserSwitchIntentReceiver; + BroadcastReceiver mNotificationTimeoutReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker; @@ -677,6 +684,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(), any(), intentFilterCaptor.capture(), any(), any()); verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(), + intentFilterCaptor.capture(), anyInt()); + verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(), intentFilterCaptor.capture()); List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues(); List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues(); @@ -695,9 +704,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mUserSwitchIntentReceiver = broadcastReceivers.get(i); } } + if (filter.hasAction(ACTION_NOTIFICATION_TIMEOUT) + && filter.hasDataScheme(SCHEME_TIMEOUT)) { + mNotificationTimeoutReceiver = broadcastReceivers.get(i); + } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver); + assertNotNull("Notification timeout receiver should exist", mNotificationTimeoutReceiver); // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); @@ -2430,6 +2444,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testCancelWithTagDoesNotCancelLifetimeExtended() throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + final NotificationRecord notif = generateNotificationRecord(null); + notif.getSbn().getNotification().flags = + Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(notif); + final StatusBarNotification sbn = notif.getSbn(); + + assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1); + assertThat(mService.getNotificationRecordCount()).isEqualTo(1); + + mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), + sbn.getUserId()); + waitForIdle(); + + assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1); + assertThat(mService.getNotificationRecordCount()).isEqualTo(1); + + mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), + sbn.getUserId()); + waitForIdle(); + + assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(0); + assertThat(mService.getNotificationRecordCount()).isEqualTo(0); + } + + @Test + public void testCancelAllDoesNotCancelLifetimeExtended() throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + // Adds a lifetime extended notification. + final NotificationRecord notif = generateNotificationRecord(mTestNotificationChannel, 1, + null, false); + notif.getSbn().getNotification().flags = + Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(notif); + // Adds a second, non-lifetime extended notification. + final NotificationRecord notifCancelable = generateNotificationRecord( + mTestNotificationChannel, 2, null, false); + mService.addNotification(notifCancelable); + // Verify that both notifications have been posted and are active. + assertThat(mBinderService.getActiveNotifications(PKG).length).isEqualTo(2); + + mBinderService.cancelAllNotifications(PKG, notif.getSbn().getUserId()); + waitForIdle(); + + // The non-lifetime extended notification, with id = 2, has been cancelled. + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertThat(notifs.length).isEqualTo(1); + assertThat(notifs[0].getId()).isEqualTo(1); + } + + @Test public void testCancelNotificationWithTag_fromApp_cannotCancelFgsChild() throws Exception { when(mAmi.applyForegroundServiceNotification( @@ -2832,6 +2899,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt() + throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + + final NotificationRecord notif = generateNotificationRecord( + mTestNotificationChannel, 1, null, false); + notif.getNotification().flags = FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(notif); + + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); + assertThat(notifs.length).isEqualTo(1); + } + + @Test public void testCancelNotificationsFromListener_byKey_GroupWithOngoingParent() throws Exception { final NotificationRecord parent = generateNotificationRecord( @@ -3036,6 +3121,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testCancelNotificationsFromListener_byKey_NoClearLifetimeExt() + throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + final NotificationRecord notif = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(notif); + String[] keys = {notif.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test public void testGroupInstanceIds() throws Exception { final NotificationRecord group1 = generateNotificationRecord( mTestNotificationChannel, 1, "group1", true); @@ -5298,6 +5399,79 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { anyInt()); } + private void simulateNotificationTimeoutBroadcast(String notificationKey) { + final Bundle extras = new Bundle(); + extras.putString(EXTRA_KEY, notificationKey); + final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT); + intent.putExtras(extras); + mNotificationTimeoutReceiver.onReceive(getContext(), intent); + } + + @Test + public void testTimeout_CancelsNotification() throws Exception { + final NotificationRecord notif = generateNotificationRecord( + mTestNotificationChannel, 1, null, false); + mService.addNotification(notif); + + simulateNotificationTimeoutBroadcast(notif.getKey()); + waitForIdle(); + + // Check that the notification was cancelled. + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertThat(notifsAfter.length).isEqualTo(0); + assertThat(mService.getNotificationRecord(notif.getKey())).isNull(); + } + + @Test + public void testTimeout_NoCancelForegroundServiceNotification() throws Exception { + // Creates a notification with FLAG_FOREGROUND_SERVICE + final NotificationRecord notif = generateNotificationRecord(null); + notif.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; + mService.addNotification(notif); + + simulateNotificationTimeoutBroadcast(notif.getKey()); + waitForIdle(); + + // Check that the notification was not cancelled. + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertThat(notifsAfter.length).isEqualTo(1); + assertThat(mService.getNotificationRecord(notif.getKey())).isEqualTo(notif); + } + + @Test + public void testTimeout_NoCancelUserInitJobNotification() throws Exception { + // Create a notification with FLAG_USER_INITIATED_JOB + final NotificationRecord notif = generateNotificationRecord(null); + notif.getSbn().getNotification().flags = Notification.FLAG_USER_INITIATED_JOB; + mService.addNotification(notif); + + simulateNotificationTimeoutBroadcast(notif.getKey()); + waitForIdle(); + + // Check that the notification was not cancelled. + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertThat(notifsAfter.length).isEqualTo(1); + assertThat(mService.getNotificationRecord(notif.getKey())).isEqualTo(notif); + } + + @Test + public void testTimeout_NoCancelLifetimeExtensionNotification() throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + // Create a notification with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY + final NotificationRecord notif = generateNotificationRecord(null); + notif.getSbn().getNotification().flags = + Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(notif); + + simulateNotificationTimeoutBroadcast(notif.getKey()); + waitForIdle(); + + // Check that the notification was not cancelled. + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertThat(notifsAfter.length).isEqualTo(1); + assertThat(mService.getNotificationRecord(notif.getKey())).isEqualTo(notif); + } + @Test public void testBumpFGImportance_channelChangePreOApp() throws Exception { String preOPkg = PKG_N_MR1; @@ -7913,6 +8087,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testOnNotificationSmartReplySent() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final int replyIndex = 2; final String reply = "Hello"; final boolean modifiedBeforeSending = true; @@ -7930,6 +8105,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED, mNotificationRecordLogger.event(0)); + // Check that r.recordSmartReplied was called. + assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) + .isGreaterThan(0); + assertThat(r.getStats().hasSmartReplied()).isTrue(); } @Test @@ -13116,6 +13295,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { eq("package"), anyString(), anyInt(), anyBoolean()); } + @Test + public void testFixNotification_clearsLifetimeExtendedFlag() throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + Notification n = new Notification.Builder(mContext, "test") + .setFlag(FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true) + .build(); + + assertThat(n.flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); + + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); + + assertThat(n.flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); + } + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index f83a1df358bd..670d09795e12 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -29,6 +29,8 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.Flags; import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationChannel; @@ -64,6 +67,7 @@ import android.os.Build; import android.os.Bundle; import android.os.UserHandle; import android.os.Vibrator; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; @@ -80,6 +84,7 @@ import com.android.server.UiServiceTestCase; import com.android.server.uri.UriGrantsManagerInternal; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -122,6 +127,9 @@ public class NotificationRecordTest extends UiServiceTestCase { private static final NotificationRecord.Light CUSTOM_LIGHT = new NotificationRecord.Light(1, 2, 3); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -651,6 +659,7 @@ public class NotificationRecordTest extends UiServiceTestCase { @Test public void testNotificationStats() { + mSetFlagsRule.enableFlags(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, false /* lights */, false /* defaultLights */, groupId /* group */); @@ -690,6 +699,37 @@ public class NotificationRecordTest extends UiServiceTestCase { record.recordDirectReplied(); assertTrue(record.getStats().hasDirectReplied()); + + record.recordSmartReplied(); + assertThat(record.getStats().hasSmartReplied()).isTrue(); + } + + @Test + public void testDirectRepliedAddsLifetimeExtensionFlag() { + mSetFlagsRule.enableFlags(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + record.recordDirectReplied(); + assertThat(record.getSbn().getNotification().flags + & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); + } + + @Test + public void testSmartRepliedAddsLifetimeExtensionFlag() { + mSetFlagsRule.enableFlags(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + record.recordSmartReplied(); + assertThat(record.getSbn().getNotification().flags + & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); } @Test diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index f2a1fe859634..c3074bb0fee8 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -93,8 +93,6 @@ android:showWhenLocked="true"/> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> - <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" /> - <activity android:name="com.android.server.wm.SurfaceSyncGroupTests$TestActivity" android:screenOrientation="locked" android:turnScreenOn="true" @@ -122,6 +120,13 @@ <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity" android:exported="true"> </activity> + + <activity android:name="com.android.server.wm.utils.TestActivity" + android:screenOrientation="locked" + android:turnScreenOn="true" + android:showWhenLocked="true" + android:theme="@style/WhiteBackgroundTheme" + android:exported="true" /> </application> <instrumentation diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml index f8ebeaddcb7e..46e87dceb8d4 100644 --- a/services/tests/wmtests/AndroidTest.xml +++ b/services/tests/wmtests/AndroidTest.xml @@ -30,4 +30,8 @@ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false" /> </test> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="settings put secure immersive_mode_confirmations confirmed" /> + </target_preparer> </configuration> diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index 2c35cf0c4151..8d236eda5dc5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -110,24 +110,6 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { } /** - * META + SPACE to switch keyboard layout. - */ - @Test - public void testMetaSpace() { - sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SPACE}, 0); - mPhoneWindowManager.assertSwitchKeyboardLayout(1); - } - - /** - * META + SHIFT + SPACE to switch keyboard layout backwards. - */ - @Test - public void testMetaShiftSpace() { - sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, 0); - mPhoneWindowManager.assertSwitchKeyboardLayout(-1); - } - - /** * CTRL + ALT + Z to enable accessibility service. */ @Test diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java index 71098aa5e883..360fdf3ae3f7 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java @@ -132,13 +132,6 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { {"LANGUAGE_SWITCH key -> Switch Keyboard Language", new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH}, KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0}, - {"Meta + Space -> Switch Keyboard Language", - new int[]{META_KEY, KeyEvent.KEYCODE_SPACE}, - KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE, META_ON}, - {"Meta + Shift + Space -> Switch Keyboard Language", - new int[]{META_KEY, SHIFT_KEY, KeyEvent.KEYCODE_SPACE}, - KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE, - META_ON | SHIFT_ON}, {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY}, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON}, {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY}, diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 9f584911aed7..9efbe3587ab2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -25,8 +25,6 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; import static com.android.server.wm.utils.LastCallVerifier.lastCall; import static org.junit.Assert.assertNotNull; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -36,11 +34,14 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.view.SurfaceControl; import android.view.SurfaceSession; import com.android.server.testutils.StubTransaction; import com.android.server.wm.utils.MockAnimationAdapter; +import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -122,7 +123,8 @@ public class DimmerTests extends WindowTestsBase { } } - static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory { + static class MockAnimationAdapterFactory extends DimmerAnimationHelper.AnimationAdapterFactory { + @Override public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, SurfaceAnimationRunner runner) { return sTestAnimation; @@ -175,8 +177,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() { - assumeTrue(Dimmer.DIMMER_REFACTOR); final float alpha = 0.7f; final int blur = 50; mHost.addChild(mChild, 0); @@ -195,8 +197,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() { - assumeFalse(Dimmer.DIMMER_REFACTOR); final float alpha = 0.7f; mHost.addChild(mChild, 0); mDimmer.adjustAppearance(mChild, alpha, 20); @@ -210,8 +212,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() { - assumeTrue(Dimmer.DIMMER_REFACTOR); mHost.addChild(mChild, 0); final float alpha = 0.8f; @@ -230,8 +232,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() { - assumeFalse(Dimmer.DIMMER_REFACTOR); mHost.addChild(mChild, 0); final float alpha = 0.8f; @@ -290,8 +292,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testRemoveDimImmediately_Smooth() { - assumeTrue(Dimmer.DIMMER_REFACTOR); mHost.addChild(mChild, 0); mDimmer.adjustAppearance(mChild, 1, 2); mDimmer.adjustRelativeLayer(mChild, -1); @@ -310,8 +312,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testRemoveDimImmediately_Legacy() { - assumeFalse(Dimmer.DIMMER_REFACTOR); mHost.addChild(mChild, 0); mDimmer.adjustAppearance(mChild, 1, 0); mDimmer.adjustRelativeLayer(mChild, -1); @@ -330,8 +332,8 @@ public class DimmerTests extends WindowTestsBase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) public void testDimmerWithBlurUpdatesTransaction_Legacy() { - assumeFalse(Dimmer.DIMMER_REFACTOR); mHost.addChild(mChild, 0); final int blurRadius = 50; @@ -344,4 +346,120 @@ public class DimmerTests extends WindowTestsBase { verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius); verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1); } + + /** + * mChild is requesting the dim values to be set directly. In this case, dim won't play the + * standard animation, but directly apply mChild's requests to the dim surface + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) + public void testContainerDimsOpeningAnimationByItself() { + mHost.addChild(mChild, 0); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(mChild, 0.1f, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(mChild, 0.2f, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + mDimmer.updateDims(mTransaction); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(mChild, 0.3f, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + mDimmer.updateDims(mTransaction); + + verify(mTransaction).setAlpha(dimLayer, 0.2f); + verify(mTransaction).setAlpha(dimLayer, 0.3f); + verify(sTestAnimation, times(1)).startAnimation( + any(SurfaceControl.class), any(SurfaceControl.Transaction.class), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + } + + /** + * Same as testContainerDimsOpeningAnimationByItself, but this is a more specific case in which + * alpha is animated to 0. This corner case is needed to verify that the layer is removed anyway + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) + public void testContainerDimsClosingAnimationByItself() { + mHost.addChild(mChild, 0); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(mChild, 0.2f, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(mChild, 0.1f, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + mDimmer.updateDims(mTransaction); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(mChild, 0f, 0); + mDimmer.adjustRelativeLayer(mChild, -1); + mDimmer.updateDims(mTransaction); + + mDimmer.resetDimStates(); + mDimmer.updateDims(mTransaction); + verify(mTransaction).remove(dimLayer); + } + + /** + * Check the handover of the dim between two windows and the consequent dim animation in between + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) + public void testMultipleContainersDimmingConsecutively() { + TestWindowContainer first = mChild; + TestWindowContainer second = new TestWindowContainer(mWm); + mHost.addChild(first, 0); + mHost.addChild(second, 1); + + mDimmer.adjustAppearance(first, 0.5f, 0); + mDimmer.adjustRelativeLayer(first, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + + mDimmer.resetDimStates(); + mDimmer.adjustAppearance(second, 0.9f, 0); + mDimmer.adjustRelativeLayer(second, -1); + mDimmer.updateDims(mTransaction); + + verify(sTestAnimation, times(2)).startAnimation( + any(SurfaceControl.class), any(SurfaceControl.Transaction.class), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction).setAlpha(dimLayer, 0.5f); + verify(mTransaction).setAlpha(dimLayer, 0.9f); + } + + /** + * Two windows are trying to modify the dim at the same time, but only the last request before + * updateDims will be satisfied + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER) + public void testMultipleContainersDimmingAtTheSameTime() { + TestWindowContainer first = mChild; + TestWindowContainer second = new TestWindowContainer(mWm); + mHost.addChild(first, 0); + mHost.addChild(second, 1); + + mDimmer.adjustAppearance(first, 0.5f, 0); + mDimmer.adjustRelativeLayer(first, -1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.adjustAppearance(second, 0.9f, 0); + mDimmer.adjustRelativeLayer(second, -1); + mDimmer.updateDims(mTransaction); + + verify(sTestAnimation, times(1)).startAnimation( + any(SurfaceControl.class), any(SurfaceControl.Transaction.class), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction, never()).setAlpha(dimLayer, 0.5f); + verify(mTransaction).setAlpha(dimLayer, 0.9f); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java index 8119fd486a87..3b9ed2652610 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java @@ -23,15 +23,14 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static org.junit.Assert.assertTrue; -import android.app.Activity; import android.app.Instrumentation; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; -import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.server.wm.BuildUtils; import android.view.Gravity; import android.view.IWindow; import android.view.SurfaceControl; @@ -46,12 +45,12 @@ import android.widget.Button; import android.widget.FrameLayout; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import com.android.server.wm.utils.CommonUtils; +import com.android.server.wm.utils.TestActivity; import org.junit.After; import org.junit.Before; @@ -59,11 +58,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @Presubmit @SmallTest @RunWith(WindowTestRunner.class) public class SurfaceControlViewHostTests { + private static final long WAIT_TIME_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER; + private static final String TAG = "SurfaceControlViewHostTests"; private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>( @@ -76,6 +78,8 @@ public class SurfaceControlViewHostTests { private SurfaceControlViewHost mScvh1; private SurfaceControlViewHost mScvh2; + private SurfaceView mSurfaceView; + @Before public void setUp() throws Exception { mInstrumentation = InstrumentationRegistry.getInstrumentation(); @@ -96,15 +100,17 @@ public class SurfaceControlViewHostTests { mView1 = new Button(mActivity); mView2 = new Button(mActivity); - mInstrumentation.runOnMainSync(() -> { - try { - mActivity.attachToSurfaceView(sc); - } catch (InterruptedException e) { - } + CountDownLatch svReadyLatch = new CountDownLatch(1); + mActivity.runOnUiThread(() -> addSurfaceView(svReadyLatch)); + assertTrue("Failed to wait for SV to get created", + svReadyLatch.await(WAIT_TIME_S, TimeUnit.SECONDS)); + new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl()) + .show(sc).apply(); + mInstrumentation.runOnMainSync(() -> { TestWindowlessWindowManager wwm = new TestWindowlessWindowManager( mActivity.getResources().getConfiguration(), sc, - mActivity.mSurfaceView.getHostToken()); + mSurfaceView.getHostToken()); mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), wwm, "requestFocusWithMultipleWindows"); @@ -135,7 +141,7 @@ public class SurfaceControlViewHostTests { } assertTrue("Failed to wait for view2", wasVisible); - IWindow window = IWindow.Stub.asInterface(mActivity.mSurfaceView.getWindowToken()); + IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken()); WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window, mScvh1.getInputTransferToken(), true); @@ -162,43 +168,30 @@ public class SurfaceControlViewHostTests { } } - public static class TestActivity extends Activity implements SurfaceHolder.Callback { - private SurfaceView mSurfaceView; - private final CountDownLatch mSvReadyLatch = new CountDownLatch(1); + private void addSurfaceView(CountDownLatch svReadyLatch) { + final FrameLayout content = mActivity.getParentLayout(); + mSurfaceView = new SurfaceView(mActivity); + mSurfaceView.setZOrderOnTop(true); + final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500, + Gravity.LEFT | Gravity.TOP); + mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + svReadyLatch.countDown(); + } - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final FrameLayout content = new FrameLayout(this); - mSurfaceView = new SurfaceView(this); - mSurfaceView.setBackgroundColor(Color.BLACK); - mSurfaceView.setZOrderOnTop(true); - final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500, - Gravity.LEFT | Gravity.TOP); - content.addView(mSurfaceView, lp); - setContentView(content); - mSurfaceView.getHolder().addCallback(this); - } + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, + int height) { - @Override - public void surfaceCreated(@NonNull SurfaceHolder holder) { - mSvReadyLatch.countDown(); - } + } - @Override - public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, - int height) { - } + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - @Override - public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - } - - public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException { - mSvReadyLatch.await(); - new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl()) - .show(sc).apply(); - } + } + }); + content.addView(mSurfaceView, lp); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java index 3cb4a1d7b7ec..e65a9feed1aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import android.os.Handler; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; @@ -39,6 +41,9 @@ class SystemServiceTestsBase { public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule( this::onBeforeSystemServicesCreated); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @WindowTestRunner.MethodWrapperRule public final WindowManagerGlobalLockRule mLockRule = new WindowManagerGlobalLockRule(mSystemServicesTestRule); 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 b1def8d0b3a6..51f0404e2396 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -444,7 +444,9 @@ public class SystemServicesTestRule implements TestRule { SurfaceAnimationThread.dispose(); AnimationThread.dispose(); UiThread.dispose(); - mInputChannel.dispose(); + if (mInputChannel != null) { + mInputChannel.dispose(); + } tearDownLocalServices(); // Reset priority booster because animation thread has been changed. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 8a90f127f4eb..06f29c262b42 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -25,7 +25,9 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; @@ -1759,6 +1761,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_createTaskFragmentDecorSurface() { + // TODO(b/293654166) remove system organizer requirement once security review is cleared. + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + + final TaskFragment tf = createTaskFragment(task); + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE).build(); + mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); + + assertApplyTransactionAllowed(mTransaction); + + verify(task).moveOrCreateDecorSurfaceFor(tf); + } + + @Test + public void testApplyTransaction_removeTaskFragmentDecorSurface() { + // TODO(b/293654166) remove system organizer requirement once security review is cleared. + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + final TaskFragment tf = createTaskFragment(task); + + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE).build(); + mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); + + assertApplyTransactionAllowed(mTransaction); + + verify(task).removeDecorSurface(); + } + + @Test public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); @@ -1966,7 +2002,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Setups the mock Task as the parent of the given TaskFragment. */ private static void setupMockParent(TaskFragment taskFragment, Task mockParent) { doReturn(mockParent).when(taskFragment).getTask(); - doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true, true)) + doReturn(new TaskFragmentParentInfo( + new Configuration(), DEFAULT_DISPLAY, true, true, null /* decorSurface */)) .when(mockParent).getTaskFragmentParentInfo(); // Task needs to be visible 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 5e531b4cbc4f..da7612b17dc9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -63,6 +63,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.app.ActivityManager; @@ -82,6 +83,7 @@ import android.util.DisplayMetrics; import android.util.Xml; import android.view.Display; import android.view.DisplayInfo; +import android.view.SurfaceControl; import android.window.TaskFragmentOrganizer; import androidx.test.filters.MediumTest; @@ -1619,6 +1621,185 @@ public class TaskTests extends WindowTestsBase { assertFalse(task.isDragResizing()); } + @Test + public void testMoveOrCreateDecorSurface() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopMostActivity(); + final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer); + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + + // Decor surface should not be present initially. + assertNull(task.mDecorSurfaceContainer); + assertNull(task.getDecorSurface()); + assertNull(task.getTaskFragmentParentInfo().getDecorSurface()); + + // Decor surface should be created. + clearInvocations(task); + task.moveOrCreateDecorSurfaceFor(fragment); + + assertNotNull(task.mDecorSurfaceContainer); + assertNotNull(task.getDecorSurface()); + verify(task).sendTaskFragmentParentInfoChangedIfNeeded(); + assertNotNull(task.getTaskFragmentParentInfo().getDecorSurface()); + assertEquals(fragment, task.mDecorSurfaceContainer.mOwnerTaskFragment); + + // Decor surface should be removed. + clearInvocations(task); + task.removeDecorSurface(); + + assertNull(task.mDecorSurfaceContainer); + assertNull(task.getDecorSurface()); + verify(task).sendTaskFragmentParentInfoChangedIfNeeded(); + assertNull(task.getTaskFragmentParentInfo().getDecorSurface()); + } + + @Test + public void testMoveOrCreateDecorSurface_whenOwnerTaskFragmentRemoved() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopMostActivity(); + final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer); + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + + task.moveOrCreateDecorSurfaceFor(fragment1); + + assertNotNull(task.mDecorSurfaceContainer); + assertNotNull(task.getDecorSurface()); + assertEquals(fragment1, task.mDecorSurfaceContainer.mOwnerTaskFragment); + + // Transfer ownership + task.moveOrCreateDecorSurfaceFor(fragment2); + + assertNotNull(task.mDecorSurfaceContainer); + assertNotNull(task.getDecorSurface()); + assertEquals(fragment2, task.mDecorSurfaceContainer.mOwnerTaskFragment); + + // Safe surface should be removed when the owner TaskFragment is removed. + task.removeChild(fragment2); + + verify(task).removeDecorSurface(); + assertNull(task.mDecorSurfaceContainer); + assertNull(task.getDecorSurface()); + } + + @Test + public void testAssignChildLayers_decorSurfacePlacement() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord unembeddedActivity = task.getTopMostActivity(); + + final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + spyOn(unembeddedActivity); + spyOn(fragment1); + spyOn(fragment2); + + // Initially, the decor surface should not be placed. + task.assignChildLayers(t); + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(fragment2).assignLayer(t, 2); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed just above the owner TaskFragment. + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.moveOrCreateDecorSurfaceFor(fragment1); + task.assignChildLayers(t); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2); + verify(fragment2).assignLayer(t, 3); + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true); + verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be invisible if the owner TaskFragment is invisible. + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(false).when(fragment1).isVisible(); + + task.assignChildLayers(t); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2); + verify(fragment2).assignLayer(t, 3); + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, false); + verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed on below activity from a different UID. + doReturn(false).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.assignChildLayers(t); + + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 0); + verify(unembeddedActivity).assignLayer(t, 1); + verify(fragment1).assignLayer(t, 2); + verify(fragment2).assignLayer(t, 3); + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true); + verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed below untrusted embedded TaskFragment. + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.assignChildLayers(t); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2); + verify(fragment2).assignLayer(t, 3); + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true); + verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should not be placed after removal. + task.removeDecorSurface(); + task.assignChildLayers(t); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(fragment2).assignLayer(t, 2); + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } 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 dade3b91e0eb..71447e72de8c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -2015,6 +2015,9 @@ public class TransitionTests extends WindowTestsBase { transition.collect(leafTaskA); rootTaskA.moveToFront("test", leafTaskA); + // Test has order changes, a shallow check of order changes + assertTrue(transition.hasOrderChanges()); + // All the tasks were already visible, so there shouldn't be any changes ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets( participants, changes); diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java new file mode 100644 index 000000000000..c12dcddd1b36 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 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.utils; + +import static android.view.WindowInsets.Type.displayCutout; +import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.os.Bundle; +import android.view.WindowInsetsController; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import androidx.annotation.Nullable; + +/** + * TestActivity that will ensure it dismisses keyguard and shows as a fullscreen activity. + */ +public class TestActivity extends Activity { + private static final int sTypeMask = systemBars() | displayCutout(); + private FrameLayout mParentLayout; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mParentLayout = new FrameLayout(this); + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT); + setContentView(mParentLayout, layoutParams); + + WindowInsetsController windowInsetsController = getWindow().getInsetsController(); + windowInsetsController.hide(sTypeMask); + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + getWindow().setAttributes(params); + getWindow().setDecorFitsSystemWindows(false); + + final KeyguardManager keyguardManager = getInstrumentation().getContext().getSystemService( + KeyguardManager.class); + if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { + keyguardManager.requestDismissKeyguard(this, null); + } + } + + public FrameLayout getParentLayout() { + return mParentLayout; + } +} diff --git a/telephony/java/android/telephony/CellularIdentifierDisclosure.java b/telephony/java/android/telephony/CellularIdentifierDisclosure.java new file mode 100644 index 000000000000..7b2db6d59819 --- /dev/null +++ b/telephony/java/android/telephony/CellularIdentifierDisclosure.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2023 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.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * A single occurrence of a cellular identifier being disclosed in the clear before a security + * context is established. + * + * @hide + */ +public final class CellularIdentifierDisclosure implements Parcelable { + private static final String TAG = "CellularIdentifierDisclosure"; + + private @NasProtocolMessage int mNasProtocolMessage; + private @CellularIdentifier int mCellularIdentifier; + private String mPlmn; + private boolean mIsEmergency; + + public CellularIdentifierDisclosure(@NasProtocolMessage int nasProtocolMessage, + @CellularIdentifier int cellularIdentifier, String plmn, boolean isEmergency) { + mNasProtocolMessage = nasProtocolMessage; + mCellularIdentifier = cellularIdentifier; + mPlmn = plmn; + mIsEmergency = isEmergency; + } + + private CellularIdentifierDisclosure(Parcel in) { + readFromParcel(in); + } + + public @NasProtocolMessage int getNasProtocolMessage() { + return mNasProtocolMessage; + } + + public @CellularIdentifier int getCellularIdentifier() { + return mCellularIdentifier; + } + + public String getPlmn() { + return mPlmn; + } + + public boolean isEmergency() { + return mIsEmergency; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mNasProtocolMessage); + out.writeInt(mCellularIdentifier); + out.writeBoolean(mIsEmergency); + out.writeString8(mPlmn); + } + + public static final Parcelable.Creator<CellularIdentifierDisclosure> CREATOR = + new Parcelable.Creator<CellularIdentifierDisclosure>() { + public CellularIdentifierDisclosure createFromParcel(Parcel in) { + return new CellularIdentifierDisclosure(in); + } + + public CellularIdentifierDisclosure[] newArray(int size) { + return new CellularIdentifierDisclosure[size]; + } + }; + + @Override + public String toString() { + return TAG + ":{ mNasProtocolMessage = " + mNasProtocolMessage + + " mCellularIdentifier = " + mCellularIdentifier + " mIsEmergency = " + + mIsEmergency + " mPlmn = " + mPlmn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CellularIdentifierDisclosure)) return false; + CellularIdentifierDisclosure that = (CellularIdentifierDisclosure) o; + return mNasProtocolMessage == that.mNasProtocolMessage + && mCellularIdentifier == that.mCellularIdentifier + && mIsEmergency == that.mIsEmergency && mPlmn.equals(that.mPlmn); + } + + @Override + public int hashCode() { + return Objects.hash(mNasProtocolMessage, mCellularIdentifier, mIsEmergency, + mPlmn); + } + + private void readFromParcel(@NonNull Parcel in) { + mNasProtocolMessage = in.readInt(); + mCellularIdentifier = in.readInt(); + mIsEmergency = in.readBoolean(); + mPlmn = in.readString8(); + } + + public static final int NAS_PROTOCOL_MESSAGE_UNKNOWN = 0; + public static final int NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST = 1; + public static final int NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE = 2; + public static final int NAS_PROTOCOL_MESSAGE_DETACH_REQUEST = 3; + public static final int NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST = 4; + public static final int NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST = 5; + public static final int NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE = 6; + public static final int NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST = 7; + public static final int NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST = 8; + public static final int NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST = 9; + public static final int NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST = 10; + public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"NAS_PROTOCOL_MESSAGE_"}, value = {NAS_PROTOCOL_MESSAGE_UNKNOWN, + NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE, + NAS_PROTOCOL_MESSAGE_DETACH_REQUEST, NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST, + NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST, + NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE, + NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST, NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST, + NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST, + NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST, NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION}) + public @interface NasProtocolMessage { + } + + public static final int CELLULAR_IDENTIFIER_UNKNOWN = 0; + public static final int CELLULAR_IDENTIFIER_IMSI = 1; + public static final int CELLULAR_IDENTIFIER_IMEI = 2; + public static final int CELLULAR_IDENTIFIER_SUCI = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CELLULAR_IDENTIFIER_"}, value = {CELLULAR_IDENTIFIER_UNKNOWN, + CELLULAR_IDENTIFIER_IMSI, CELLULAR_IDENTIFIER_IMEI, CELLULAR_IDENTIFIER_SUCI}) + public @interface CellularIdentifier { + } +} diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 4548a7df6874..1e5f33f26dc2 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -710,9 +710,15 @@ public class GraphicsActivity extends Activity { float childFrameRate = Collections.max(frameRates); float parentFrameRate = childFrameRate / 2; int initialNumEvents = mModeChangedEvents.size(); - parent.setFrameRate(parentFrameRate); parent.setFrameRateSelectionStrategy(parentStrategy); - child.setFrameRate(childFrameRate); + + // For Self case, we want to test that child gets default behavior + if (parentStrategy == SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF) { + parent.setFrameRateCategory(Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE); + } else { + parent.setFrameRate(parentFrameRate); + child.setFrameRate(childFrameRate); + } // Verify float expectedFrameRate = diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java index bed9cff75e1d..29f6879f37c3 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java @@ -16,11 +16,8 @@ package android.view.surfacecontroltests; -import static org.junit.Assume.assumeTrue; - import android.Manifest; import android.hardware.display.DisplayManager; -import android.os.SystemProperties; import android.support.test.uiautomator.UiDevice; import android.view.Surface; import android.view.SurfaceControl; @@ -54,10 +51,6 @@ public class SurfaceControlTest { UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - // TODO(b/290634611): clean this up once SF new front end is enabled by default - assumeTrue(SystemProperties.getBoolean( - "persist.debug.sf.enable_layer_lifecycle_manager", false)); - uiDevice.wakeUp(); uiDevice.executeShellCommand("wm dismiss-keyguard"); @@ -118,10 +111,11 @@ public class SurfaceControlTest { } @Test - public void testSurfaceControlFrameRateSelectionStrategySelf() throws InterruptedException { + public void testSurfaceControlFrameRateSelectionStrategyPropagate() + throws InterruptedException { GraphicsActivity activity = mActivityRule.getActivity(); activity.testSurfaceControlFrameRateSelectionStrategy( - SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF); + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE); } @Test @@ -131,4 +125,12 @@ public class SurfaceControlTest { activity.testSurfaceControlFrameRateSelectionStrategy( SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } + + @Test + public void testSurfaceControlFrameRateSelectionStrategySelf() + throws InterruptedException { + GraphicsActivity activity = mActivityRule.getActivity(); + activity.testSurfaceControlFrameRateSelectionStrategy( + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF); + } } diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh index 85038be80c51..91e6814ed243 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh @@ -51,6 +51,8 @@ ANNOTATION_FILTER=$TEMP/annotation-filter.txt HOSTSTUBGEN_OUT=$TEMP/output.txt +EXTRA_ARGS="" + # Because of `set -e`, we can't return non-zero from functions, so we store # HostStubGen result in it. HOSTSTUBGEN_RC=0 @@ -115,6 +117,7 @@ run_hoststubgen() { --keep-static-initializer-annotation \ android.hosttest.annotation.HostSideTestStaticInitializerKeep \ $filter_arg \ + $EXTRA_ARGS \ |& tee $HOSTSTUBGEN_OUT HOSTSTUBGEN_RC=${PIPESTATUS[0]} echo "HostStubGen exited with $HOSTSTUBGEN_RC" @@ -209,7 +212,6 @@ com.supported.* com.unsupported.* " - run_hoststubgen_for_failure "One specific class disallowed" \ "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \ " @@ -229,6 +231,14 @@ IMPL="" run_hoststubgen_for_success "No impl generation" "" STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" "" +EXTRA_ARGS="--in-jar abc" run_hoststubgen_for_failure "Duplicate arg" \ + "Duplicate or conflicting argument found: --in-jar" \ + "" + +EXTRA_ARGS="--quiet" run_hoststubgen_for_failure "Conflicting arg" \ + "Duplicate or conflicting argument found: --quiet" \ + "" + echo "All tests passed" exit 0
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 3cdddc23b332..dbcf3a5207e5 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -22,7 +22,6 @@ import com.android.hoststubgen.filters.ConstantFilter import com.android.hoststubgen.filters.DefaultHookInjectingFilter import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.ImplicitOutputFilter -import com.android.hoststubgen.filters.KeepAllClassesFilter import com.android.hoststubgen.filters.OutputFilter import com.android.hoststubgen.filters.StubIntersectingFilter import com.android.hoststubgen.filters.createFilterFromTextPolicyFile @@ -52,15 +51,15 @@ class HostStubGen(val options: HostStubGenOptions) { val errors = HostStubGenErrors() // Load all classes. - val allClasses = loadClassStructures(options.inJar) + val allClasses = loadClassStructures(options.inJar.get) // Dump the classes, if specified. - options.inputJarDumpFile?.let { + options.inputJarDumpFile.ifSet { PrintWriter(it).use { pw -> allClasses.dump(pw) } log.i("Dump file created at $it") } - options.inputJarAsKeepAllFile?.let { + options.inputJarAsKeepAllFile.ifSet { PrintWriter(it).use { pw -> allClasses.forEach { classNode -> printAsTextPolicy(pw, classNode) @@ -74,11 +73,11 @@ class HostStubGen(val options: HostStubGenOptions) { // Transform the jar. convert( - options.inJar, - options.outStubJar, - options.outImplJar, + options.inJar.get, + options.outStubJar.get, + options.outImplJar.get, filter, - options.enableClassChecker, + options.enableClassChecker.get, allClasses, errors, ) @@ -153,7 +152,7 @@ class HostStubGen(val options: HostStubGenOptions) { // text-file based filter, which is handled by parseTextFilterPolicyFile. // The first filter is for the default policy from the command line options. - var filter: OutputFilter = ConstantFilter(options.defaultPolicy, "default-by-options") + var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options") // Next, we need a filter that resolves "class-wide" policies. // This is used when a member (methods, fields, nested classes) don't get any polices @@ -163,16 +162,16 @@ class HostStubGen(val options: HostStubGenOptions) { // Inject default hooks from options. filter = DefaultHookInjectingFilter( - options.defaultClassLoadHook, - options.defaultMethodCallHook, + options.defaultClassLoadHook.get, + options.defaultMethodCallHook.get, filter ) - val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.let { filename -> - if (filename == null) { + val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.get.let { file -> + if (file == null) { ClassFilter.newNullFilter(true) // Allow all classes } else { - ClassFilter.loadFromFile(filename, false) + ClassFilter.loadFromFile(file, false) } } @@ -196,7 +195,7 @@ class HostStubGen(val options: HostStubGenOptions) { // Next, "text based" filter, which allows to override polices without touching // the target code. - options.policyOverrideFile?.let { + options.policyOverrideFile.ifSet { filter = createFilterFromTextPolicyFile(it, allClasses, filter) } @@ -212,11 +211,6 @@ class HostStubGen(val options: HostStubGenOptions) { // Apply the implicit filter. filter = ImplicitOutputFilter(errors, allClasses, filter) - // Optionally keep all classes. - if (options.keepAllClasses) { - filter = KeepAllClassesFilter(filter) - } - return filter } @@ -422,9 +416,9 @@ class HostStubGen(val options: HostStubGenOptions) { outVisitor = CheckClassAdapter(outVisitor) } val visitorOptions = BaseAdapter.Options( - enablePreTrace = options.enablePreTrace, - enablePostTrace = options.enablePostTrace, - enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection, + enablePreTrace = options.enablePreTrace.get, + enablePostTrace = options.enablePostTrace.get, + enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get, errors = errors, ) outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 83f873d38f1b..0ae52afb73e4 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -21,21 +21,60 @@ import java.io.File import java.io.FileReader /** + * A single value that can only set once. + */ +class SetOnce<T>( + private var value: T, +) { + class SetMoreThanOnceException : Exception() + + private var set = false + + fun set(v: T) { + if (set) { + throw SetMoreThanOnceException() + } + if (v == null) { + throw NullPointerException("This shouldn't happen") + } + set = true + value = v + } + + val get: T + get() = this.value + + val isSet: Boolean + get() = this.set + + fun <R> ifSet(block: (T & Any) -> R): R? { + if (isSet) { + return block(value!!) + } + return null + } + + override fun toString(): String { + return "$value" + } +} + +/** * Options that can be set from command line arguments. */ class HostStubGenOptions( /** Input jar file*/ - var inJar: String = "", + var inJar: SetOnce<String> = SetOnce(""), /** Output stub jar file */ - var outStubJar: String? = null, + var outStubJar: SetOnce<String?> = SetOnce(null), /** Output implementation jar file */ - var outImplJar: String? = null, + var outImplJar: SetOnce<String?> = SetOnce(null), - var inputJarDumpFile: String? = null, + var inputJarDumpFile: SetOnce<String?> = SetOnce(null), - var inputJarAsKeepAllFile: String? = null, + var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null), var stubAnnotations: MutableSet<String> = mutableSetOf(), var keepAnnotations: MutableSet<String> = mutableSetOf(), @@ -51,27 +90,26 @@ class HostStubGenOptions( var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(), - var annotationAllowedClassesFile: String? = null, + var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null), - var defaultClassLoadHook: String? = null, - var defaultMethodCallHook: String? = null, + var defaultClassLoadHook: SetOnce<String?> = SetOnce(null), + var defaultMethodCallHook: SetOnce<String?> = SetOnce(null), var intersectStubJars: MutableSet<String> = mutableSetOf(), - var policyOverrideFile: String? = null, + var policyOverrideFile: SetOnce<String?> = SetOnce(null), - var defaultPolicy: FilterPolicy = FilterPolicy.Remove, - var keepAllClasses: Boolean = false, + var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove), - var logLevel: LogLevel = LogLevel.Info, + var logLevel: SetOnce<LogLevel> = SetOnce(LogLevel.Info), - var cleanUpOnError: Boolean = false, + var cleanUpOnError: SetOnce<Boolean> = SetOnce(true), - var enableClassChecker: Boolean = false, - var enablePreTrace: Boolean = false, - var enablePostTrace: Boolean = false, + var enableClassChecker: SetOnce<Boolean> = SetOnce(false), + var enablePreTrace: SetOnce<Boolean> = SetOnce(false), + var enablePostTrace: SetOnce<Boolean> = SetOnce(false), - var enableNonStubMethodCallDetection: Boolean = false, + var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false), ) { companion object { @@ -111,110 +149,120 @@ class HostStubGenOptions( break } - when (arg) { - // TODO: Write help - "-h", "--h" -> TODO("Help is not implemented yet") + // Define some shorthands... + fun nextArg(): String = ai.nextArgRequired(arg) + fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) } + fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) } + fun MutableSet<String>.addUniqueAnnotationArg(): String = + nextArg().also { this += ensureUniqueAnnotation(it) } + + try { + when (arg) { + // TODO: Write help + "-h", "--help" -> TODO("Help is not implemented yet") - "-v", "--verbose" -> ret.logLevel = LogLevel.Verbose - "-d", "--debug" -> ret.logLevel = LogLevel.Debug - "-q", "--quiet" -> ret.logLevel = LogLevel.None + "-v", "--verbose" -> ret.logLevel.set(LogLevel.Verbose) + "-d", "--debug" -> ret.logLevel.set(LogLevel.Debug) + "-q", "--quiet" -> ret.logLevel.set(LogLevel.None) - "--in-jar" -> ret.inJar = ai.nextArgRequired(arg).ensureFileExists() - "--out-stub-jar" -> ret.outStubJar = ai.nextArgRequired(arg) - "--out-impl-jar" -> ret.outImplJar = ai.nextArgRequired(arg) + "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists() + "--out-stub-jar" -> ret.outStubJar.setNextStringArg() + "--out-impl-jar" -> ret.outImplJar.setNextStringArg() - "--policy-override-file" -> - ret.policyOverrideFile = ai.nextArgRequired(arg).ensureFileExists() + "--policy-override-file" -> + ret.policyOverrideFile.setNextStringArg().ensureFileExists() - "--clean-up-on-error" -> ret.cleanUpOnError = true - "--no-clean-up-on-error" -> ret.cleanUpOnError = false + "--clean-up-on-error" -> ret.cleanUpOnError.set(true) + "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false) - "--default-remove" -> ret.defaultPolicy = FilterPolicy.Remove - "--default-throw" -> ret.defaultPolicy = FilterPolicy.Throw - "--default-keep" -> ret.defaultPolicy = FilterPolicy.Keep - "--default-stub" -> ret.defaultPolicy = FilterPolicy.Stub + "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove) + "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw) + "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep) + "--default-stub" -> ret.defaultPolicy.set(FilterPolicy.Stub) - "--keep-all-classes" -> ret.keepAllClasses = true - "--no-keep-all-classes" -> ret.keepAllClasses = false + "--stub-annotation" -> + ret.stubAnnotations.addUniqueAnnotationArg() - "--stub-annotation" -> - ret.stubAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--keep-annotation" -> + ret.keepAnnotations.addUniqueAnnotationArg() - "--keep-annotation" -> - ret.keepAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--stub-class-annotation" -> + ret.stubClassAnnotations.addUniqueAnnotationArg() - "--stub-class-annotation" -> - ret.stubClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--keep-class-annotation" -> + ret.keepClassAnnotations.addUniqueAnnotationArg() - "--keep-class-annotation" -> - ret.keepClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--throw-annotation" -> + ret.throwAnnotations.addUniqueAnnotationArg() - "--throw-annotation" -> - ret.throwAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--remove-annotation" -> + ret.removeAnnotations.addUniqueAnnotationArg() - "--remove-annotation" -> - ret.removeAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--substitute-annotation" -> + ret.substituteAnnotations.addUniqueAnnotationArg() - "--substitute-annotation" -> - ret.substituteAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--native-substitute-annotation" -> + ret.nativeSubstituteAnnotations.addUniqueAnnotationArg() - "--native-substitute-annotation" -> - ret.nativeSubstituteAnnotations += - ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--class-load-hook-annotation" -> + ret.classLoadHookAnnotations.addUniqueAnnotationArg() - "--class-load-hook-annotation" -> - ret.classLoadHookAnnotations += - ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--keep-static-initializer-annotation" -> + ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg() - "--keep-static-initializer-annotation" -> - ret.keepStaticInitializerAnnotations += - ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--package-redirect" -> + ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg)) - "--package-redirect" -> - ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg)) + "--annotation-allowed-classes-file" -> + ret.annotationAllowedClassesFile.setNextStringArg() - "--annotation-allowed-classes-file" -> - ret.annotationAllowedClassesFile = ai.nextArgRequired(arg) + "--default-class-load-hook" -> + ret.defaultClassLoadHook.setNextStringArg() - "--default-class-load-hook" -> - ret.defaultClassLoadHook = ai.nextArgRequired(arg) + "--default-method-call-hook" -> + ret.defaultMethodCallHook.setNextStringArg() - "--default-method-call-hook" -> - ret.defaultMethodCallHook = ai.nextArgRequired(arg) + "--intersect-stub-jar" -> + ret.intersectStubJars += nextArg().ensureFileExists() - "--intersect-stub-jar" -> - ret.intersectStubJars += ai.nextArgRequired(arg).ensureFileExists() + "--gen-keep-all-file" -> + ret.inputJarAsKeepAllFile.setNextStringArg() - "--gen-keep-all-file" -> - ret.inputJarAsKeepAllFile = ai.nextArgRequired(arg) + // Following options are for debugging. + "--enable-class-checker" -> ret.enableClassChecker.set(true) + "--no-class-checker" -> ret.enableClassChecker.set(false) - // Following options are for debugging. - "--enable-class-checker" -> ret.enableClassChecker = true - "--no-class-checker" -> ret.enableClassChecker = false + "--enable-pre-trace" -> ret.enablePreTrace.set(true) + "--no-pre-trace" -> ret.enablePreTrace.set(false) - "--enable-pre-trace" -> ret.enablePreTrace = true - "--no-pre-trace" -> ret.enablePreTrace = false + "--enable-post-trace" -> ret.enablePostTrace.set(true) + "--no-post-trace" -> ret.enablePostTrace.set(false) - "--enable-post-trace" -> ret.enablePostTrace = true - "--no-post-trace" -> ret.enablePostTrace = false + "--enable-non-stub-method-check" -> + ret.enableNonStubMethodCallDetection.set(true) - "--enable-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = true - "--no-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = false + "--no-non-stub-method-check" -> + ret.enableNonStubMethodCallDetection.set(false) - "--gen-input-dump-file" -> ret.inputJarDumpFile = ai.nextArgRequired(arg) + "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg() - else -> throw ArgumentsException("Unknown option: $arg") + else -> throw ArgumentsException("Unknown option: $arg") + } + } catch (e: SetOnce.SetMoreThanOnceException) { + throw ArgumentsException("Duplicate or conflicting argument found: $arg") } } - if (ret.inJar.isEmpty()) { + log.w(ret.toString()) + + if (!ret.inJar.isSet) { throw ArgumentsException("Required option missing: --in-jar") } - if (ret.outStubJar == null && ret.outImplJar == null) { + if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) { log.w("Neither --out-stub-jar nor --out-impl-jar is set." + " $COMMAND_NAME will not generate jar files.") } - if (ret.enableNonStubMethodCallDetection) { + if (ret.enableNonStubMethodCallDetection.get) { log.w("--enable-non-stub-method-check is not fully implemented yet." + " See the todo in doesMethodNeedNonStubCallCheck().") } @@ -329,7 +377,6 @@ class HostStubGenOptions( intersectStubJars=$intersectStubJars, policyOverrideFile=$policyOverrideFile, defaultPolicy=$defaultPolicy, - keepAllClasses=$keepAllClasses, logLevel=$logLevel, cleanUpOnError=$cleanUpOnError, enableClassChecker=$enableClassChecker, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt index 0321d9db03ed..38ba0ccfd14f 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt @@ -28,9 +28,9 @@ fun main(args: Array<String>) { try { // Parse the command line arguments. val options = HostStubGenOptions.parseArgs(args) - clanupOnError = options.cleanUpOnError + clanupOnError = options.cleanUpOnError.get - log.level = options.logLevel + log.level = options.logLevel.get log.v("HostStubGen started") log.v("Options: $options") diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 356e1fa1327c..8354d985abfd 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -39,5 +39,5 @@ class AndroidHeuristicsFilter( private fun ClassNodes.isAidlClass(className: String): Boolean { return hasClass(className) && hasClass("$className\$Stub") && - hasClass("$className\$Proxy") + hasClass("$className\$Stub\$Proxy") }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 214de59bbb4d..3956893ee7ed 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -223,16 +223,16 @@ RuntimeVisibleAnnotations: java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD] ) -## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class Compiled from "IPretendingAidl.java" -public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 - public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -243,7 +243,7 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy; + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy; public static int addTwo(int); descriptor: (I)I @@ -262,7 +262,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy SourceFile: "IPretendingAidl.java" NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class Compiled from "IPretendingAidl.java" public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub @@ -303,6 +304,7 @@ SourceFile: "IPretendingAidl.java" NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class Compiled from "IPretendingAidl.java" public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl @@ -315,11 +317,11 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl } SourceFile: "IPretendingAidl.java" NestMembers: - com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl - public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index 90312289db1c..9349355d4d02 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -1,13 +1,13 @@ -## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class Compiled from "IPretendingAidl.java" -public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 4 - public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -30,7 +30,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy x: athrow } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -71,6 +72,7 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub } InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -89,8 +91,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl interfaces: 0, fields: 0, methods: 0, attributes: 4 } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl - public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -98,8 +100,8 @@ RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestMembers: - com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index e01f49baf320..4f8c408eb7d9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -205,16 +205,16 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) -## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class Compiled from "IPretendingAidl.java" -public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 4 - public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -225,7 +225,7 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy; + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy; public static int addTwo(int); descriptor: (I)I @@ -242,7 +242,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy 0 4 0 a I } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -288,6 +289,7 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub } InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -306,8 +308,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl interfaces: 0, fields: 0, methods: 0, attributes: 4 } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl - public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -315,8 +317,8 @@ RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestMembers: - com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index 90312289db1c..9349355d4d02 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -1,13 +1,13 @@ -## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class Compiled from "IPretendingAidl.java" -public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 4 - public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: @@ -30,7 +30,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy x: athrow } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -71,6 +72,7 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub } InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -89,8 +91,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl interfaces: 0, fields: 0, methods: 0, attributes: 4 } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl - public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -98,8 +100,8 @@ RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestMembers: - com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index 5246355fb777..5ff3cded38b3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -289,13 +289,13 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) -## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class Compiled from "IPretendingAidl.java" -public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 3, attributes: 4 private static {}; @@ -303,17 +303,17 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy flags: (0x000a) ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=0, args_size=0 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V x: return - public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy x: ldc #x // String <init> x: ldc #x // String ()V x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall @@ -324,14 +324,14 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy LineNumberTable: LocalVariableTable: Start Length Slot Name Signature - 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy; + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy; public static int addTwo(int); descriptor: (I)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 - x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy x: ldc #x // String addTwo x: ldc #x // String (I)I x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall @@ -346,7 +346,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy 11 4 0 a I } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -412,6 +413,7 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub } InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -439,8 +441,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl x: return } InnerClasses: - public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub SourceFile: "IPretendingAidl.java" RuntimeVisibleAnnotations: x: #x() @@ -448,8 +450,8 @@ RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestMembers: - com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java index 583e13c5573b..0a07c2b91fc3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java @@ -25,11 +25,12 @@ public interface IPretendingAidl { public static int addOne(int a) { return a + 1; } - } - public static class Proxy { - public static int addTwo(int a) { - return a + 2; + public static class Proxy { + public static int addTwo(int a) { + return a + 2; + } } } + } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 0d527915ef63..d3501057163d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -289,6 +289,6 @@ public class TinyFrameworkClassTest { @Test public void testAidlHeuristics() { assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2); - assertThat(IPretendingAidl.Proxy.addTwo(1)).isEqualTo(3); + assertThat(IPretendingAidl.Stub.Proxy.addTwo(1)).isEqualTo(3); } } |