diff options
637 files changed, 14832 insertions, 6770 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..e6c94d896e50 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 = @@ -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 9d13d8ad7905..805ecd43e3c7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9342,6 +9342,7 @@ package android.app.usage { method public String getClassName(); method public android.content.res.Configuration getConfiguration(); method public int getEventType(); + method @FlaggedApi("android.app.usage.user_interaction_type_api") @NonNull public android.os.PersistableBundle getExtras(); method public String getPackageName(); method public String getShortcutId(); method public long getTimeStamp(); @@ -9407,6 +9408,8 @@ package android.app.usage { method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery); method public android.app.usage.UsageEvents queryEventsForSelf(long, long); method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long); + field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION"; + field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_CATEGORY = "android.app.usage.extra.EVENT_CATEGORY"; field public static final int INTERVAL_BEST = 4; // 0x4 field public static final int INTERVAL_DAILY = 0; // 0x0 field public static final int INTERVAL_MONTHLY = 2; // 0x2 @@ -9851,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); @@ -10598,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"; @@ -10610,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"; @@ -11098,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"; @@ -12363,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); @@ -12419,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); @@ -12440,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 @@ -12455,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 { @@ -12618,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; @@ -12647,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; @@ -12769,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); @@ -13010,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 @@ -22031,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); @@ -27299,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 { @@ -32837,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); @@ -33376,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; 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 f3bad3aa82e0..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 { @@ -4201,8 +4220,13 @@ package android.window { public static class WindowInfosListenerForTest.WindowInfo { field @NonNull public final android.graphics.Rect bounds; field public final int displayId; + field public final boolean isDuplicateTouchToWallpaper; + field public final boolean isFocusable; + field public final boolean isPreventSplitting; + field public final boolean isTouchable; field public final boolean isTrustedOverlay; field public final boolean isVisible; + field public final boolean isWatchOutsideTouch; field @NonNull public final String name; field @NonNull public final android.graphics.Matrix transform; field @NonNull public final android.os.IBinder windowToken; 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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index df6badcffe8e..d54074818b41 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -328,7 +328,7 @@ interface IActivityTaskManager { * A splash screen view has copied. */ void onSplashScreenViewCopyFinished(int taskId, - in SplashScreenView.SplashScreenViewParcelable material); + in @nullable SplashScreenView.SplashScreenViewParcelable material); /** * When the Picture-in-picture state has changed. 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/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index ebd5d649fc2b..cf191784b728 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -22,6 +22,7 @@ import android.app.usage.BroadcastResponseStatsList; import android.app.usage.UsageEvents; import android.app.usage.UsageEventsQuery; import android.content.pm.ParceledListSlice; +import android.os.PersistableBundle; /** * System private API for talking with the UsageStatsManagerService. @@ -77,6 +78,8 @@ interface IUsageStatsManager { String callingPackage); void reportUsageStop(in IBinder activity, String token, String callingPackage); void reportUserInteraction(String packageName, int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)") + void reportUserInteractionWithBundle(String packageName, int userId, in PersistableBundle eventExtras); int getUsageSource(); void forceUsageSourceSettingRead(); long getLastTimeAnyComponentUsed(String packageName, String callingPackage); diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java index aa3239237478..7bc6cb9f616a 100644 --- a/core/java/android/app/usage/ParcelableUsageEventList.java +++ b/core/java/android/app/usage/ParcelableUsageEventList.java @@ -223,6 +223,7 @@ public final class ParcelableUsageEventList implements Parcelable { event.mContentAnnotations = null; event.mNotificationChannelId = null; event.mLocusId = null; + event.mExtras = null; switch (event.mEventType) { case Event.CONFIGURATION_CHANGE -> { @@ -237,6 +238,11 @@ public final class ParcelableUsageEventList implements Parcelable { case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt(); case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString(); case Event.LOCUS_ID_SET -> event.mLocusId = in.readString(); + case Event.USER_INTERACTION -> { + if (in.readInt() != 0) { + event.mExtras = in.readPersistableBundle(getClass().getClassLoader()); + } + } } event.mFlags = in.readInt(); @@ -263,6 +269,14 @@ public final class ParcelableUsageEventList implements Parcelable { case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason); case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId); case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId); + case Event.USER_INTERACTION -> { + if (event.mExtras != null) { + dest.writeInt(1); + dest.writePersistableBundle(event.mExtras); + } else { + dest.writeInt(0); + } + } } dest.writeInt(event.mFlags); } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 6c7eba06dea7..9eb73b32a2a7 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -16,7 +16,9 @@ package android.app.usage; import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -24,6 +26,7 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; import android.util.Log; import java.lang.annotation.Retention; @@ -551,6 +554,22 @@ public final class UsageEvents implements Parcelable { public int mLocusIdToken = UNASSIGNED_TOKEN; /** @hide */ + public PersistableBundle mExtras = null; + + /** @hide */ + public static class UserInteractionEventExtrasToken { + public int mCategoryToken = UNASSIGNED_TOKEN; + public int mActionToken = UNASSIGNED_TOKEN; + + public UserInteractionEventExtrasToken() { + // Do nothing. + } + } + + /** @hide */ + public UserInteractionEventExtrasToken mUserInteractionExtrasToken = null; + + /** @hide */ @EventFlags public int mFlags; @@ -650,6 +669,21 @@ public final class UsageEvents implements Parcelable { } /** + * Retrieves a map of extended data from the event if the event is of type + * {@link #USER_INTERACTION}. + * + * @return the map of all extras that associated with the reported user interaction + * event. The returned {@link PersistableBundle} will contain the extras + * {@link UsageStatsManager#EXTRA_EVENT_CATEGORY} and + * {@link UsageStatsManager#EXTRA_EVENT_ACTION}. {@link PersistableBundle#EMPTY} + * will be returned if the details are not available. + */ + @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API) + public @NonNull PersistableBundle getExtras() { + return mExtras == null ? PersistableBundle.EMPTY : mExtras; + } + + /** * Returns a {@link Configuration} for this event if the event is of type * {@link #CONFIGURATION_CHANGE}, otherwise it returns null. */ @@ -747,6 +781,7 @@ public final class UsageEvents implements Parcelable { mBucketAndReason = orig.mBucketAndReason; mNotificationChannelId = orig.mNotificationChannelId; mLocusId = orig.mLocusId; + mExtras = orig.mExtras; } } @@ -802,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(); } } @@ -987,6 +1023,14 @@ public final class UsageEvents implements Parcelable { case Event.LOCUS_ID_SET: p.writeString(event.mLocusId); break; + case Event.USER_INTERACTION: + if (event.mExtras != null) { + p.writeInt(1); + p.writePersistableBundle(event.mExtras); + } else { + p.writeInt(0); + } + break; } p.writeInt(event.mFlags); } @@ -1036,6 +1080,7 @@ public final class UsageEvents implements Parcelable { eventOut.mContentAnnotations = null; eventOut.mNotificationChannelId = null; eventOut.mLocusId = null; + eventOut.mExtras = null; switch (eventOut.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -1059,6 +1104,11 @@ public final class UsageEvents implements Parcelable { case Event.LOCUS_ID_SET: eventOut.mLocusId = p.readString(); break; + case Event.USER_INTERACTION: + if (p.readInt() != 0) { + eventOut.mExtras = p.readPersistableBundle(getClass().getClassLoader()); + } + break; } eventOut.mFlags = p.readInt(); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 4f1c993bfa1a..85d223d01ee8 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -28,6 +28,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserHandleAware; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.BroadcastOptions; import android.app.PendingIntent; @@ -35,6 +36,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Build; +import android.os.PersistableBundle; import android.os.PowerWhitelistManager; import android.os.RemoteException; import android.os.UserHandle; @@ -392,6 +394,23 @@ public final class UsageStatsManager { @SystemApi public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED"; + /** + * A String extra, when used with {@link UsageEvents.Event#getExtras}, that indicates + * the category of the user interaction associated with the event. The category cannot + * be more than 127 characters, longer value will be truncated to 127 characters. + */ + @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API) + public static final String EXTRA_EVENT_CATEGORY = + "android.app.usage.extra.EVENT_CATEGORY"; + + /** + * A String extra, when used with {@link UsageEvents.Event#getExtras}, that indicates + * the action of the user interaction associated with the event. The action cannot be + * more than 127 characters, longer value will be truncated to 127 characters. + */ + @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API) + public static final String EXTRA_EVENT_ACTION = + "android.app.usage.extra.EVENT_ACTION"; /** * App usage observers will consider the task root package the source of usage. @@ -562,10 +581,10 @@ public final class UsageStatsManager { * then {@code null} will be returned.</em> * * @param beginTime The inclusive beginning of the range of events to include in the results. - * Defined in terms of "Unix time", see - * {@link java.lang.System#currentTimeMillis}. + * Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. * @param endTime The exclusive end of the range of events to include in the results. Defined - * in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}. + * in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}. * @return A {@link UsageEvents}. */ public UsageEvents queryEvents(long beginTime, long endTime) { @@ -611,10 +630,10 @@ public final class UsageStatsManager { * then {@code null} will be returned.</em> * * @param beginTime The inclusive beginning of the range of events to include in the results. - * Defined in terms of "Unix time", see - * {@link java.lang.System#currentTimeMillis}. + * Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. * @param endTime The exclusive end of the range of events to include in the results. Defined - * in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}. + * in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}. * @return A {@link UsageEvents} object. * * @see #queryEvents(long, long) @@ -1128,6 +1147,7 @@ public final class UsageStatsManager { * Reports user interaction with a given package in the given user. * * <p><em>This method is only for use by the system</em> + * * @hide */ @RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS) @@ -1140,6 +1160,38 @@ public final class UsageStatsManager { } /** + * Reports user interaction with given package and a particular {@code extras} + * in the given user. + * + * <p> + * Note: The structure of {@code extras} is a {@link PersistableBundle} with the + * category {@link #EXTRA_EVENT_CATEGORY} and the action {@link #EXTRA_EVENT_ACTION}. + * Category provides additional detail about the user interaction, the value + * is defined in namespace based. Example: android.app.notification could be used to + * indicate that the reported user interaction is related to notification. Action + * indicates the general action that performed. + * </p> + * + * @param packageName The package name of the app + * @param userId The user id who triggers the user interaction + * @param extras The {@link PersistableBundle} that will be used to specify the + * extra details for the user interaction event. The {@link PersistableBundle} + * must contain the extras {@link #EXTRA_EVENT_CATEGORY}, + * {@link #EXTRA_EVENT_ACTION}. Cannot be empty. + * @hide + */ + @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API) + @RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS) + public void reportUserInteraction(@NonNull String packageName, @UserIdInt int userId, + @NonNull PersistableBundle extras) { + try { + mService.reportUserInteractionWithBundle(packageName, userId, extras); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Report usage associated with a particular {@code token} has started. Tokens are app defined * strings used to represent usage of in-app features. Apps with the {@link * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers 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/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index ca84b3563561..6a83cee10309 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -682,6 +682,7 @@ public abstract class BatteryConsumer { static class BatteryConsumerDataLayout { private static final Key[] KEY_ARRAY = new Key[0]; + public static final int POWER_MODEL_NOT_INCLUDED = -1; public final String[] customPowerComponentNames; public final int customPowerComponentCount; public final boolean powerModelsIncluded; @@ -713,7 +714,9 @@ public abstract class BatteryConsumer { // Declare the Key for the power component, ignoring other dimensions. perComponentKeys.add( new Key(componentId, PROCESS_STATE_ANY, - powerModelsIncluded ? columnIndex++ : -1, // power model + powerModelsIncluded + ? columnIndex++ + : POWER_MODEL_NOT_INCLUDED, // power model columnIndex++, // power columnIndex++ // usage duration )); @@ -736,7 +739,9 @@ public abstract class BatteryConsumer { perComponentKeys.add( new Key(componentId, processState, - powerModelsIncluded ? columnIndex++ : -1, // power model + powerModelsIncluded + ? columnIndex++ + : POWER_MODEL_NOT_INCLUDED, // power model columnIndex++, // power columnIndex++ // usage duration )); @@ -843,11 +848,27 @@ public abstract class BatteryConsumer { @SuppressWarnings("unchecked") @NonNull + public T addConsumedPower(@PowerComponent int componentId, double componentPower, + @PowerModel int powerModel) { + mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED), + componentPower, powerModel); + return (T) this; + } + + @SuppressWarnings("unchecked") + @NonNull public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) { mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel); return (T) this; } + @SuppressWarnings("unchecked") + @NonNull + public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) { + mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel); + return (T) this; + } + /** * Sets the amount of drain attributed to the specified custom drain type. * diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e85b7bf1c91e..16ffaef03121 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -4029,6 +4029,17 @@ public abstract class BatteryStats { } /** + * A helper object passed to various dump... methods to integrate with such objects + * as BatteryUsageStatsProvider. + */ + public interface BatteryStatsDumpHelper { + /** + * Generates BatteryUsageStats based on the specified BatteryStats. + */ + BatteryUsageStats getBatteryUsageStats(BatteryStats batteryStats, boolean detailed); + } + + /** * Dumps the ControllerActivityCounter if it has any data worth dumping. * The order of the arguments in the final check in line is: * @@ -4390,7 +4401,7 @@ public abstract class BatteryStats { * NOTE: all times are expressed in microseconds, unless specified otherwise. */ public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid, - boolean wifiOnly) { + boolean wifiOnly, BatteryStatsDumpHelper dumpHelper) { if (which != BatteryStats.STATS_SINCE_CHARGED) { dumpLine(pw, 0, STAT_NAMES[which], "err", @@ -4652,7 +4663,7 @@ public abstract class BatteryStats { } } - final BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */); + final BatteryUsageStats stats = dumpHelper.getBatteryUsageStats(this, true /* detailed */); dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA, formatCharge(stats.getBatteryCapacity()), formatCharge(stats.getConsumedPower()), @@ -5127,7 +5138,7 @@ public abstract class BatteryStats { @SuppressWarnings("unused") public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which, - int reqUid, boolean wifiOnly) { + int reqUid, boolean wifiOnly, BatteryStatsDumpHelper dumpHelper) { if (which != BatteryStats.STATS_SINCE_CHARGED) { pw.println("ERROR: BatteryStats.dump called for which type " + which @@ -5854,7 +5865,7 @@ public abstract class BatteryStats { pw.println(); - BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */); + BatteryUsageStats stats = dumpHelper.getBatteryUsageStats(this, true /* detailed */); stats.dump(pw, prefix); List<UidMobileRadioStats> uidMobileRadioStats = @@ -7642,10 +7653,11 @@ public abstract class BatteryStats { /** * Dumps a human-readable summary of the battery statistics to the given PrintWriter. * - * @param pw a Printer to receive the dump output. + * @param pw a Printer to receive the dump output. */ @SuppressWarnings("unused") - public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { + public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart, + BatteryStatsDumpHelper dumpHelper) { synchronized (this) { prepareForDumpLocked(); } @@ -7663,12 +7675,12 @@ public abstract class BatteryStats { } synchronized (this) { - dumpLocked(context, pw, flags, reqUid, filtering); + dumpLocked(context, pw, flags, reqUid, filtering, dumpHelper); } } private void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, - boolean filtering) { + boolean filtering, BatteryStatsDumpHelper dumpHelper) { if (!filtering) { SparseArray<? extends Uid> uidStats = getUidStats(); final int NU = uidStats.size(); @@ -7803,15 +7815,15 @@ public abstract class BatteryStats { pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid, - (flags&DUMP_DEVICE_WIFI_ONLY) != 0); + (flags & DUMP_DEVICE_WIFI_ONLY) != 0, dumpHelper); pw.println(); } } // This is called from BatteryStatsService. @SuppressWarnings("unused") - public void dumpCheckin(Context context, PrintWriter pw, - List<ApplicationInfo> apps, int flags, long histStart) { + public void dumpCheckin(Context context, PrintWriter pw, List<ApplicationInfo> apps, int flags, + long histStart, BatteryStatsDumpHelper dumpHelper) { synchronized (this) { prepareForDumpLocked(); @@ -7829,12 +7841,12 @@ public abstract class BatteryStats { } synchronized (this) { - dumpCheckinLocked(context, pw, apps, flags); + dumpCheckinLocked(context, pw, apps, flags, dumpHelper); } } private void dumpCheckinLocked(Context context, PrintWriter pw, List<ApplicationInfo> apps, - int flags) { + int flags, BatteryStatsDumpHelper dumpHelper) { if (apps != null) { SparseArray<Pair<ArrayList<String>, MutableBoolean>> uids = new SparseArray<>(); for (int i=0; i<apps.size(); i++) { @@ -7881,7 +7893,7 @@ public abstract class BatteryStats { (Object[])lineArgs); } dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1, - (flags&DUMP_DEVICE_WIFI_ONLY) != 0); + (flags & DUMP_DEVICE_WIFI_ONLY) != 0, dumpHelper); } } @@ -7891,7 +7903,7 @@ public abstract class BatteryStats { * @hide */ public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps, - int flags, long histStart) { + int flags, long histStart, BatteryStatsDumpHelper dumpHelper) { final ProtoOutputStream proto = new ProtoOutputStream(fd); prepareForDumpLocked(); @@ -7909,7 +7921,8 @@ public abstract class BatteryStats { proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion()); if ((flags & DUMP_DAILY_ONLY) == 0) { - final BatteryUsageStats stats = getBatteryUsageStats(context, false /* detailed */); + final BatteryUsageStats stats = + dumpHelper.getBatteryUsageStats(this, false /* detailed */); ProportionalAttributionCalculator proportionalAttributionCalculator = new ProportionalAttributionCalculator(context, stats); dumpProtoAppsLocked(proto, stats, apps, proportionalAttributionCalculator); @@ -8856,8 +8869,6 @@ public abstract class BatteryStats { return !tm.isDataCapable(); } - protected abstract BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed); - private boolean shouldHidePowerComponent(int powerComponent) { return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE || powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index cd52b5c0f7f3..eabe13b0c54f 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -36,6 +36,7 @@ import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -122,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; @@ -515,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) { @@ -586,7 +609,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { + "(" + BatteryConsumer.processStateToString(key.processState) + ")"; } printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah, - deviceConsumer.getPowerModel(key), + mIncludesPowerModels ? deviceConsumer.getPowerModel(key) + : BatteryConsumer.POWER_MODEL_UNDEFINED, deviceConsumer.getUsageDurationMillis(key)); } } @@ -774,6 +798,15 @@ public final class BatteryUsageStats implements Parcelable, Closeable { super.finalize(); } + @Override + public String toString() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + dump(pw, ""); + pw.flush(); + return sw.toString(); + } + /** * Builder for BatteryUsageStats. */ 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/PowerComponents.java b/core/java/android/os/PowerComponents.java index 9e5f5399301c..9c11ad433b8f 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -15,6 +15,7 @@ */ package android.os; +import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED; import static android.os.BatteryConsumer.POWER_COMPONENT_ANY; import static android.os.BatteryConsumer.PROCESS_STATE_ANY; import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED; @@ -118,7 +119,7 @@ class PowerComponents { @BatteryConsumer.PowerModel int getPowerModel(BatteryConsumer.Key key) { - if (key.mPowerModelColumnIndex == -1) { + if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { throw new IllegalStateException( "Power model IDs were not requested in the BatteryUsageStatsQuery"); } @@ -468,7 +469,7 @@ class PowerComponents { mMinConsumedPowerThreshold = minConsumedPowerThreshold; for (BatteryConsumer.Key[] keys : mData.layout.keys) { for (BatteryConsumer.Key key : keys) { - if (key.mPowerModelColumnIndex != -1) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED); } } @@ -478,11 +479,19 @@ class PowerComponents { @NonNull public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel) { - if (Math.abs(componentPower) < mMinConsumedPowerThreshold) { - componentPower = 0; - } mData.putDouble(key.mPowerColumnIndex, componentPower); - if (key.mPowerModelColumnIndex != -1) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { + mData.putInt(key.mPowerModelColumnIndex, powerModel); + } + return this; + } + + @NonNull + public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower, + int powerModel) { + mData.putDouble(key.mPowerColumnIndex, + mData.getDouble(key.mPowerColumnIndex) + componentPower); + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { mData.putInt(key.mPowerModelColumnIndex, powerModel); } return this; @@ -496,9 +505,6 @@ class PowerComponents { */ @NonNull public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) { - if (Math.abs(componentPower) < mMinConsumedPowerThreshold) { - componentPower = 0; - } final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; if (index < 0 || index >= mData.layout.customPowerComponentCount) { throw new IllegalArgumentException( @@ -575,12 +581,12 @@ class PowerComponents { mData.getLong(key.mDurationColumnIndex) + otherData.getLong(otherKey.mDurationColumnIndex)); - if (key.mPowerModelColumnIndex == -1) { + if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { continue; } boolean undefined = false; - if (otherKey.mPowerModelColumnIndex == -1) { + if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { undefined = true; } else { final int powerModel = mData.getInt(key.mPowerModelColumnIndex); @@ -641,19 +647,26 @@ class PowerComponents { */ @NonNull public PowerComponents build() { - mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower()); - for (BatteryConsumer.Key[] keys : mData.layout.keys) { for (BatteryConsumer.Key key : keys) { - if (key.mPowerModelColumnIndex != -1) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) { mData.putInt(key.mPowerModelColumnIndex, BatteryConsumer.POWER_MODEL_UNDEFINED); } } + + if (mMinConsumedPowerThreshold != 0) { + if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) { + mData.putDouble(key.mPowerColumnIndex, 0); + } + } } } + if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) { + mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower()); + } return new PowerComponents(this); } } 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..8f18c5f58c7e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14975,6 +14975,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/InputDevice.java b/core/java/android/view/InputDevice.java index 9f886c826174..d131dc9a4c7d 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -69,6 +69,7 @@ public final class InputDevice implements Parcelable { private final String mName; private final int mVendorId; private final int mProductId; + private final int mDeviceBus; private final String mDescriptor; private final InputDeviceIdentifier mIdentifier; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -468,8 +469,8 @@ public final class InputDevice implements Parcelable { * Called by native code */ private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, - int productId, String descriptor, boolean isExternal, int sources, int keyboardType, - KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag, + int productId, int deviceBus, String descriptor, boolean isExternal, int sources, + int keyboardType, KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag, @Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor, int usiVersionMinor, int associatedDisplayId) { @@ -479,6 +480,7 @@ public final class InputDevice implements Parcelable { mName = name; mVendorId = vendorId; mProductId = productId; + mDeviceBus = deviceBus; mDescriptor = descriptor; mIsExternal = isExternal; mSources = sources; @@ -512,6 +514,7 @@ public final class InputDevice implements Parcelable { mName = in.readString(); mVendorId = in.readInt(); mProductId = in.readInt(); + mDeviceBus = in.readInt(); mDescriptor = in.readString(); mIsExternal = in.readInt() != 0; mSources = in.readInt(); @@ -551,6 +554,7 @@ public final class InputDevice implements Parcelable { private String mName = ""; private int mVendorId = 0; private int mProductId = 0; + private int mDeviceBus = 0; private String mDescriptor = ""; private boolean mIsExternal = false; private int mSources = 0; @@ -604,6 +608,12 @@ public final class InputDevice implements Parcelable { return this; } + /** @see InputDevice#getDeviceBus() */ + public Builder setDeviceBus(int deviceBus) { + mDeviceBus = deviceBus; + return this; + } + /** @see InputDevice#getDescriptor() */ public Builder setDescriptor(String descriptor) { mDescriptor = descriptor; @@ -705,6 +715,7 @@ public final class InputDevice implements Parcelable { mName, mVendorId, mProductId, + mDeviceBus, mDescriptor, mIsExternal, mSources, @@ -847,6 +858,21 @@ public final class InputDevice implements Parcelable { } /** + * Gets the device bus used by given device, if available. + * <p> + * The device bus is the communication system used for transferring data + * (e.g. USB, Bluetooth etc.). This value comes from the kernel (from input.h). + * A value of 0 will be assigned where the device bus is not available. + * </p> + * + * @return The device bus of a given device + * @hide + */ + public int getDeviceBus() { + return mDeviceBus; + } + + /** * Gets the input device descriptor, which is a stable identifier for an input device. * <p> * An input device descriptor uniquely identifies an input device. Its value @@ -1448,6 +1474,7 @@ public final class InputDevice implements Parcelable { out.writeString(mName); out.writeInt(mVendorId); out.writeInt(mProductId); + out.writeInt(mDeviceBus); out.writeString(mDescriptor); out.writeInt(mIsExternal ? 1 : 0); out.writeInt(mSources); 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/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java index 35ce72620d09..34c639974bfd 100644 --- a/core/java/android/window/WindowInfosListenerForTest.java +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -19,6 +19,7 @@ package android.window; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.graphics.Matrix; import android.graphics.Rect; @@ -87,6 +88,38 @@ public class WindowInfosListenerForTest { @NonNull public final Matrix transform; + /** + * True if the window is touchable. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isTouchable; + + /** + * True if the window is focusable. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isFocusable; + + /** + * True if the window is preventing splitting + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isPreventSplitting; + + /** + * True if the window duplicates touches received to wallpaper. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isDuplicateTouchToWallpaper; + + /** + * True if the window is listening for when there is a touch DOWN event + * occurring outside its touchable bounds. When such an event occurs, + * this window will receive a MotionEvent with ACTION_OUTSIDE. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isWatchOutsideTouch; + WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId, @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) { this.windowToken = windowToken; @@ -96,6 +129,14 @@ public class WindowInfosListenerForTest { this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0; this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0; this.transform = transform; + this.isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) == 0; + this.isFocusable = (inputConfig & InputConfig.NOT_FOCUSABLE) == 0; + this.isPreventSplitting = (inputConfig + & InputConfig.PREVENT_SPLITTING) != 0; + this.isDuplicateTouchToWallpaper = (inputConfig + & InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER) != 0; + this.isWatchOutsideTouch = (inputConfig + & InputConfig.WATCH_OUTSIDE_TOUCH) != 0; } @Override diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 7f93213bf884..29932f342b74 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -48,3 +48,11 @@ flag { is_fixed_read_only: true bug: "262477923" } + +flag { + namespace: "window_surfaces" + name: "secure_window_state" + description: "Move SC secure flag to WindowState level" + is_fixed_read_only: true + bug: "308662081" +} 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/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java index d0d2354e7007..661628a8c148 100644 --- a/core/java/com/android/internal/os/MonotonicClock.java +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -50,6 +50,8 @@ public class MonotonicClock { private final Clock mClock; private long mTimeshift; + public static final long UNDEFINED = -1; + public MonotonicClock(File file) { mFile = new AtomicFile(file); mClock = Clock.SYSTEM_CLOCK; 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/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 262f5e8e761e..239c6260800b 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -81,7 +81,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi deviceInfo.getId(), deviceInfo.getGeneration(), deviceInfo.getControllerNumber(), nameObj.get(), static_cast<int32_t>(ident.vendor), - static_cast<int32_t>(ident.product), descriptorObj.get(), + static_cast<int32_t>(ident.product), + static_cast<int32_t>(ident.bus), descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(), keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(), @@ -111,7 +112,7 @@ int register_android_view_InputDevice(JNIEnv* env) gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz); gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>", - "(IIILjava/lang/String;IILjava/lang/" + "(IIILjava/lang/String;IIILjava/lang/" "String;ZIILandroid/view/KeyCharacterMap;Ljava/" "lang/String;Ljava/lang/String;ZZZZZIII)V"); 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/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto index d5cf60d94e3a..c242c9ca3218 100644 --- a/core/proto/android/server/usagestatsservice_v2.proto +++ b/core/proto/android/server/usagestatsservice_v2.proto @@ -110,6 +110,7 @@ message EventObfuscatedProto { optional int32 task_root_package_token = 11; optional int32 task_root_class_token = 12; optional int32 locus_id_token = 13; + optional ObfuscatedUserInteractionExtrasProto interaction_extras = 14; } /** @@ -129,6 +130,7 @@ message PendingEventProto { optional string task_root_package = 11; optional string task_root_class = 12; optional string locus_id = 13 [(.android.privacy).dest = DEST_EXPLICIT]; + optional bytes extras = 14; } /** @@ -145,3 +147,11 @@ message ObfuscatedPackagesProto { // Stores the mappings for every package repeated PackagesMap packages_map = 2; } + +/** + * Store the relevant information from extra details for user interaction event. + */ +message ObfuscatedUserInteractionExtrasProto { + optional int32 category_token = 1; + optional int32 action_token = 2; +} 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/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/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java index 5a202c5e8834..9dce899aa92d 100644 --- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java +++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyString; import android.app.usage.UsageEvents.Event; import android.content.res.Configuration; import android.os.Parcel; +import android.os.PersistableBundle; import android.test.suitebuilder.annotation.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -140,6 +141,12 @@ public class ParcelableUsageEventListTest { case Event.LOCUS_ID_SET: event.mLocusId = anyString(); break; + case Event.USER_INTERACTION: + PersistableBundle extras = new PersistableBundle(); + extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, anyString()); + extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, anyString()); + event.mExtras = extras; + break; } event.mFlags = anyInt(); @@ -176,6 +183,14 @@ public class ParcelableUsageEventListTest { case Event.LOCUS_ID_SET: assertEquals(ue1.mLocusId, ue2.mLocusId); break; + case Event.USER_INTERACTION: + final PersistableBundle extras1 = ue1.getExtras(); + final PersistableBundle extras2 = ue2.getExtras(); + assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY), + extras2.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY)); + assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_ACTION), + extras2.getString(UsageStatsManager.EXTRA_EVENT_ACTION)); + break; } assertEquals(ue1.mFlags, ue2.mFlags); diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java index 083e37a3aaa6..fae714842b9b 100644 --- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java +++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java @@ -72,7 +72,7 @@ public class UsageStatsPersistenceTest { "mShortcutId", "mShortcutIdToken", "mBucketAndReason", "mInstanceId", "mNotificationChannelId", "mNotificationChannelIdToken", "mTaskRootPackage", "mTaskRootPackageToken", "mTaskRootClass", "mTaskRootClassToken", "mLocusId", - "mLocusIdToken"}; + "mLocusIdToken", "mExtras", "mUserInteractionExtrasToken"}; // All fields in this list are defined in UsageEvents.Event but not persisted private static final String[] USAGEEVENTS_IGNORED_FIELDS = {"mAction", "mContentAnnotations", "mContentType", "DEVICE_EVENT_PACKAGE_NAME", "FLAG_IS_PACKAGE_INSTANT_APP", 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 f19acbe023b8..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", @@ -991,12 +1003,6 @@ "group": "WM_DEBUG_WINDOW_INSETS", "at": "com\/android\/server\/wm\/InsetsSourceProvider.java" }, - "-1176488860": { - "message": "SURFACE isSecure=%b: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "-1164930508": { "message": "Moving to RESUMED: %s (starting new instance) callers=%s", "level": "VERBOSE", @@ -1177,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", @@ -1801,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", @@ -3277,6 +3283,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "810599500": { + "message": "SURFACE isSecure=%b: %s", + "level": "INFO", + "group": "WM_SHOW_TRANSACTIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "829434921": { "message": "Draw state now committed in %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 a9dc145afabd..3e84597a375d 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -114,6 +114,13 @@ flag { } flag { + name: "unfold_animation_background_progress" + namespace: "systemui" + description: "Moves unfold animation progress calculation to a background thread" + bug: "277879146" +} + +flag { name: "qs_new_pipeline" namespace: "systemui" description: "Use the new pipeline for Quick Settings. Should have no behavior changes." @@ -157,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/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt index c9e57b45612c..b33f6fa7eadb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt @@ -32,8 +32,7 @@ constructor(private val activityManager: ActivityManager) : CurrentActivityTypeP override val isHomeActivity: Boolean? get() = _isHomeActivity - private var _isHomeActivity: Boolean? = null - + @Volatile private var _isHomeActivity: Boolean? = null override fun init() { _isHomeActivity = activityManager.isOnHomeActivity() diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt index 3b8d318a3a79..baa88897947c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt @@ -18,18 +18,19 @@ import android.content.Context import android.hardware.devicestate.DeviceStateManager import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.FoldProvider.FoldCallback +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @Singleton -class DeviceStateManagerFoldProvider @Inject constructor( - private val deviceStateManager: DeviceStateManager, - private val context: Context -) : FoldProvider { +class DeviceStateManagerFoldProvider +@Inject +constructor(private val deviceStateManager: DeviceStateManager, private val context: Context) : + FoldProvider { - private val callbacks: MutableMap<FoldCallback, - DeviceStateManager.DeviceStateCallback> = hashMapOf() + private val callbacks = + ConcurrentHashMap<FoldCallback, DeviceStateManager.DeviceStateCallback>() override fun registerCallback(callback: FoldCallback, executor: Executor) { val listener = FoldStateListener(context, callback) @@ -39,13 +40,9 @@ class DeviceStateManagerFoldProvider @Inject constructor( override fun unregisterCallback(callback: FoldCallback) { val listener = callbacks.remove(callback) - listener?.let { - deviceStateManager.unregisterCallback(it) - } + listener?.let { deviceStateManager.unregisterCallback(it) } } - private inner class FoldStateListener( - context: Context, - listener: FoldCallback - ) : DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) }) + private inner class FoldStateListener(context: Context, listener: FoldCallback) : + DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) }) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt index 7b67e3f3c920..7af991743457 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt @@ -15,24 +15,29 @@ package com.android.systemui.unfold.system import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.os.Process import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig -import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.dagger.UnfoldMain +import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import dagger.Binds import dagger.Module +import dagger.Provides import java.util.concurrent.Executor +import javax.inject.Singleton /** - * Dagger module with system-only dependencies for the unfold animation. - * The code that is used to calculate unfold transition progress - * depends on some hidden APIs that are not available in normal - * apps. In order to re-use this code and use alternative implementations - * of these classes in other apps and hidden APIs here. + * Dagger module with system-only dependencies for the unfold animation. The code that is used to + * calculate unfold transition progress depends on some hidden APIs that are not available in normal + * apps. In order to re-use this code and use alternative implementations of these classes in other + * apps and hidden APIs here. */ @Module abstract class SystemUnfoldSharedModule { @@ -61,4 +66,22 @@ abstract class SystemUnfoldSharedModule { @Binds @UnfoldSingleThreadBg abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor + + companion object { + @Provides + @UnfoldBg + @Singleton + fun unfoldBgProgressHandler(@UnfoldBg looper: Looper): Handler { + return Handler(looper) + } + + @Provides + @UnfoldBg + @Singleton + fun provideBgLooper(): Looper { + return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND) + .apply { start() } + .looper + } + } } 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/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index d9e06296e460..e7b87730f94b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -19,6 +19,7 @@ package com.android.systemui.dagger; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; +import com.android.systemui.Flags; import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactoryBase; import com.android.systemui.dagger.qualifiers.PerUser; @@ -35,6 +36,7 @@ import com.android.systemui.unfold.FoldStateLogger; import com.android.systemui.unfold.FoldStateLoggingProvider; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.unfold.dagger.UnfoldBg; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; @@ -144,7 +146,15 @@ public interface SysUIComponent { getConnectingDisplayViewModel().init(); getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); getFoldStateLogger().ifPresent(FoldStateLogger::init); - getUnfoldTransitionProgressProvider() + + Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider; + + if (Flags.unfoldAnimationBackgroundProgress()) { + unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider(); + } else { + unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider(); + } + unfoldTransitionProgressProvider .ifPresent( (progressProvider) -> getUnfoldTransitionProgressForwarder() @@ -170,7 +180,14 @@ public interface SysUIComponent { ContextComponentHelper getContextComponentHelper(); /** - * Creates a UnfoldTransitionProgressProvider. + * Creates a UnfoldTransitionProgressProvider that calculates progress in the background. + */ + @SysUISingleton + @UnfoldBg + Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider(); + + /** + * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread. */ @SysUISingleton Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider(); 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/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 26c5ea6e164d..c93b8e1a48f2 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -29,7 +29,6 @@ import com.android.app.tracing.FlowTracing.traceEach import com.android.app.tracing.traceSection import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.DisplayEvent import com.android.systemui.util.Compile @@ -93,7 +92,7 @@ class DisplayRepositoryImpl constructor( private val displayManager: DisplayManager, @Background backgroundHandler: Handler, - @Application applicationScope: CoroutineScope, + @Background bgApplicationScope: CoroutineScope, @Background backgroundCoroutineDispatcher: CoroutineDispatcher ) : DisplayRepository { private val allDisplayEvents: Flow<DisplayEvent> = @@ -141,8 +140,7 @@ constructor( private val enabledDisplays = allDisplayEvents .map { getDisplays() } - .flowOn(backgroundCoroutineDispatcher) - .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1) + .shareIn(bgApplicationScope, started = SharingStarted.WhileSubscribed(), replay = 1) override val displays: Flow<Set<Display>> = enabledDisplays @@ -203,9 +201,8 @@ constructor( } .distinctUntilChanged() .debugLog("connectedDisplayIds") - .flowOn(backgroundCoroutineDispatcher) .stateIn( - applicationScope, + bgApplicationScope, started = SharingStarted.WhileSubscribed(), // The initial value is set to empty, but connected displays are gathered as soon as // the flow starts being collected. This is to ensure the call to get displays (an 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/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt index f9b89b11cd67..7354cfcb170d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.app.tracing.traceSection +import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import javax.inject.Singleton @@ -29,7 +30,7 @@ class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenL screenLifecycle.addObserver(this) } - private val listeners: MutableList<ScreenListener> = mutableListOf() + private val listeners: MutableList<ScreenListener> = CopyOnWriteArrayList() override fun removeCallback(listener: ScreenListener) { listeners.remove(listener) 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/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index 8ae093a531c2..10fc83c8b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -20,6 +20,7 @@ import com.android.keyguard.KeyguardUnfoldTransition import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.NotificationPanelUnfoldAnimationController import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager @@ -33,10 +34,7 @@ import java.util.Optional import javax.inject.Named import javax.inject.Scope -@Scope -@MustBeDocumented -@Retention(AnnotationRetention.RUNTIME) -annotation class SysUIUnfoldScope +@Scope @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SysUIUnfoldScope /** * Creates an injectable [SysUIUnfoldComponent] that provides objects that have been scoped with @@ -55,20 +53,21 @@ class SysUIUnfoldModule { @Provides @SysUISingleton fun provideSysUIUnfoldComponent( - provider: Optional<UnfoldTransitionProgressProvider>, - rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>, - @Named(UNFOLD_STATUS_BAR) scopedProvider: - Optional<ScopedUnfoldTransitionProgressProvider>, - unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>, - factory: SysUIUnfoldComponent.Factory + provider: Optional<UnfoldTransitionProgressProvider>, + rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>, + @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>, + @UnfoldBg bgProvider: Optional<UnfoldTransitionProgressProvider>, + unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>, + factory: SysUIUnfoldComponent.Factory ): Optional<SysUIUnfoldComponent> { val p1 = provider.getOrNull() val p2 = rotationProvider.getOrNull() val p3 = scopedProvider.getOrNull() - return if (p1 == null || p2 == null || p3 == null) { + val p4 = bgProvider.getOrNull() + return if (p1 == null || p2 == null || p3 == null || p4 == null) { Optional.empty() } else { - Optional.of(factory.create(p1, p2, p3, unfoldLatencyTracker.get())) + Optional.of(factory.create(p1, p2, p3, p4, unfoldLatencyTracker.get())) } } } @@ -76,13 +75,15 @@ class SysUIUnfoldModule { @SysUIUnfoldScope @Subcomponent interface SysUIUnfoldComponent { + @Subcomponent.Factory interface Factory { fun create( - @BindsInstance p1: UnfoldTransitionProgressProvider, - @BindsInstance p2: NaturalRotationUnfoldProgressProvider, - @BindsInstance p3: ScopedUnfoldTransitionProgressProvider, - @BindsInstance p4: UnfoldLatencyTracker, + @BindsInstance p1: UnfoldTransitionProgressProvider, + @BindsInstance p2: NaturalRotationUnfoldProgressProvider, + @BindsInstance p3: ScopedUnfoldTransitionProgressProvider, + @BindsInstance @UnfoldBg p4: UnfoldTransitionProgressProvider, + @BindsInstance p5: UnfoldLatencyTracker, ): SysUIUnfoldComponent } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 36a1e8a072c9..b72c6f189e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -35,6 +35,9 @@ import android.view.SurfaceControlViewHost import android.view.SurfaceSession import android.view.WindowManager import android.view.WindowlessWindowManager +import com.android.app.tracing.traceSection +import com.android.keyguard.logging.ScrimLogger +import com.android.systemui.Flags.unfoldAnimationBackgroundProgress import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -45,16 +48,16 @@ import com.android.systemui.statusbar.LinearLightRevealEffect import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled import com.android.systemui.util.concurrency.ThreadFactory -import com.android.app.tracing.traceSection -import com.android.keyguard.logging.ScrimLogger import com.android.wm.shell.displayareahelper.DisplayAreaHelper import java.util.Optional import java.util.concurrent.Executor import java.util.function.Consumer import javax.inject.Inject +import javax.inject.Provider @SysUIUnfoldScope class UnfoldLightRevealOverlayAnimation @@ -65,11 +68,14 @@ constructor( private val deviceStateManager: DeviceStateManager, private val contentResolver: ContentResolver, private val displayManager: DisplayManager, - private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + @UnfoldBg + private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>, + private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>, private val displayAreaHelper: Optional<DisplayAreaHelper>, @Main private val executor: Executor, private val threadFactory: ThreadFactory, - private val rotationChangeProvider: RotationChangeProvider, + @UnfoldBg private val rotationChangeProvider: RotationChangeProvider, + @UnfoldBg private val unfoldProgressHandler: Handler, private val displayTracker: DisplayTracker, private val scrimLogger: ScrimLogger, ) { @@ -96,11 +102,15 @@ constructor( fun init() { // This method will be called only on devices where this animation is enabled, // so normally this thread won't be created - bgHandler = threadFactory.buildHandlerOnNewThread(TAG) + bgHandler = unfoldProgressHandler bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler) deviceStateManager.registerCallback(bgExecutor, FoldListener()) - unfoldTransitionProgressProvider.addCallback(transitionListener) + if (unfoldAnimationBackgroundProgress()) { + unfoldTransitionBgProgressProvider.get().addCallback(transitionListener) + } else { + unfoldTransitionProgressProvider.get().addCallback(transitionListener) + } rotationChangeProvider.addCallback(rotationWatcher) val containerBuilder = @@ -169,8 +179,13 @@ constructor( overlayAddReason = reason - val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, - "UnfoldLightRevealOverlayAnimation") + val newRoot = + SurfaceControlViewHost( + context, + context.display, + wwm, + "UnfoldLightRevealOverlayAnimation" + ) val params = getLayoutParams() val newView = LightRevealScrim( @@ -353,12 +368,13 @@ constructor( } private fun executeInBackground(f: () -> Unit) { - check(Looper.myLooper() != bgHandler.looper) { - "Trying to execute using background handler while already running" + - " in the background handler" + // This is needed to allow progresses to be received both from the main thread (that will + // schedule a runnable on the bg thread), and from the bg thread directly (no reposting). + if (bgHandler.looper.isCurrentThread) { + f() + } else { + bgHandler.post(f) } - // The UiBackground executor is not used as it doesn't have a prepared looper. - bgHandler.post(f) } private fun ensureInBackground() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt index 12b88458d355..94912bf82377 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt @@ -21,11 +21,14 @@ import com.android.app.tracing.TraceStateLogger import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.unfold.system.DeviceStateRepository import com.android.systemui.unfold.updates.FoldStateRepository import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.plus /** * Logs several unfold related details in a trace. Mainly used for debugging and investigate @@ -37,7 +40,8 @@ class UnfoldTraceLogger constructor( private val context: Context, private val foldStateRepository: FoldStateRepository, - @Application private val applicationScope: CoroutineScope, + @Application applicationScope: CoroutineScope, + @Background private val coroutineContext: CoroutineContext, private val deviceStateRepository: DeviceStateRepository ) : CoreStartable { private val isFoldable: Boolean @@ -46,20 +50,22 @@ constructor( .getIntArray(com.android.internal.R.array.config_foldedDeviceStates) .isNotEmpty() + private val bgScope = applicationScope.plus(coroutineContext) + override fun start() { if (!isFoldable) return - applicationScope.launch { + bgScope.launch { val foldUpdateLogger = TraceStateLogger("FoldUpdate") foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) } } - applicationScope.launch { + bgScope.launch { foldStateRepository.hingeAngle.collect { Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt()) } } - applicationScope.launch { + bgScope.launch { val foldedStateLogger = TraceStateLogger("FoldedState") deviceStateRepository.isFolded.collect { isFolded -> foldedStateLogger.log( diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 7b628f8d676f..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,13 @@ 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 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor @@ -63,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 @@ -102,7 +109,7 @@ class UnfoldTransitionModule { @Singleton fun provideNaturalRotationProgressProvider( context: Context, - rotationChangeProvider: RotationChangeProvider, + @UnfoldMain rotationChangeProvider: RotationChangeProvider, unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> ): Optional<NaturalRotationUnfoldProgressProvider> = unfoldTransitionProgressProvider.map { provider -> @@ -153,7 +160,8 @@ class UnfoldTransitionModule { return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider) - } ?: ShellUnfoldProgressProvider.NO_PROVIDER + } + ?: ShellUnfoldProgressProvider.NO_PROVIDER } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt index cc9335edfc14..472f0ae364c5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.plus import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -29,6 +30,14 @@ class CoroutinesModule { @Provides @SysUISingleton + @Background + fun bgApplicationScope( + @Application applicationScope: CoroutineScope, + @Background coroutineContext: CoroutineContext, + ): CoroutineScope = applicationScope.plus(coroutineContext) + + @Provides + @SysUISingleton @Main @Deprecated( "Use @Main CoroutineContext instead", 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/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt index 9fe2f5694dde..14fb054a1d9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt @@ -15,9 +15,10 @@ */ package com.android.systemui.unfold.progress +import android.os.Handler +import android.os.HandlerThread import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED @@ -26,6 +27,8 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING import com.android.systemui.unfold.util.TestFoldStateProvider +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,16 +40,28 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { private val foldStateProvider: TestFoldStateProvider = TestFoldStateProvider() private val listener = TestUnfoldProgressListener() private lateinit var progressProvider: UnfoldTransitionProgressProvider + private val schedulerFactory = + mock<UnfoldFrameCallbackScheduler.Factory>().apply { + whenever(create()).then { UnfoldFrameCallbackScheduler() } + } + private val mockBgHandler = mock<Handler>() + private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper) @Before fun setUp() { - progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider) + progressProvider = + PhysicsBasedUnfoldTransitionProgressProvider( + context, + schedulerFactory, + foldStateProvider = foldStateProvider, + progressHandler = fakeHandler + ) progressProvider.addCallback(listener) } @Test fun testUnfold_emitsIncreasingTransitionEvents() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, @@ -63,7 +78,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testUnfold_emitsFinishingEvent() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, @@ -77,7 +92,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(90f) }, @@ -94,7 +109,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testFold_emitsDecreasingTransitionEvents() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) }, { foldStateProvider.sendHingeAngleUpdate(170f) }, { foldStateProvider.sendHingeAngleUpdate(90f) }, @@ -110,7 +125,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(10f) }, @@ -126,7 +141,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(10f) }, @@ -144,9 +159,12 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() } } - private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) { + private fun runOnProgressThreadWithInterval( + vararg blocks: () -> Unit, + intervalMillis: Long = 60, + ) { blocks.forEach { - InstrumentationRegistry.getInstrumentation().runOnMainSync { it() } + fakeHandler.post(it) Thread.sleep(intervalMillis) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index aa492871a079..552b60cbeb21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -37,7 +37,6 @@ import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenLis import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import junit.framework.Assert.fail import java.util.concurrent.Executor @@ -105,16 +104,15 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { foldStateProvider = DeviceFoldStateProvider( - config, - testHingeAngleProvider, - screenOnStatusProvider, - foldProvider, - activityTypeProvider, - unfoldKeyguardVisibilityProvider, - rotationChangeProvider, - context, - context.mainExecutor, - handler + config, + context, + screenOnStatusProvider, + activityTypeProvider, + unfoldKeyguardVisibilityProvider, + foldProvider, + testHingeAngleProvider, + rotationChangeProvider, + handler ) foldStateProvider.addCallback( @@ -151,6 +149,12 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { null } + whenever(handler.post(any<Runnable>())).then { invocationOnMock -> + val runnable = invocationOnMock.getArgument<Runnable>(0) + runnable.run() + null + } + // By default, we're on launcher. setupForegroundActivityType(isHomeActivity = true) setIsLargeScreen(true) @@ -171,7 +175,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } @Test - fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() { + fun onUnfold_angleDecrBeforeInnerScrAvailable_emitsOnlyStartAndInnerScrAvailableEvents() { setFoldState(folded = true) foldUpdates.clear() @@ -187,7 +191,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } @Test - fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() { + fun onUnfold_angleDecrAfterInnerScrAvailable_emitsStartInnerScrAvailableAndStartClosingEvnts() { setFoldState(folded = true) foldUpdates.clear() @@ -690,7 +694,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { callbacks.forEach { it.onFoldUpdated(isFolded) } } - fun getNumberOfCallbacks(): Int{ + fun getNumberOfCallbacks(): Int { return callbacks.size } } 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/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt index a639df539cb9..2bc2db3ba629 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt @@ -16,9 +16,12 @@ package com.android.systemui.unfold +import android.os.Handler import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.dagger.UseReceivingFilter import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener import dagger.Module import dagger.Provides @@ -33,16 +36,25 @@ class UnfoldRemoteModule { @Singleton fun provideTransitionProvider( config: UnfoldTransitionConfig, - traceListener: ATraceLoggerTransitionProgressListener, + traceListener: ATraceLoggerTransitionProgressListener.Factory, remoteReceiverProvider: Provider<RemoteUnfoldTransitionReceiver>, ): Optional<RemoteUnfoldTransitionReceiver> { if (!config.isEnabled) { return Optional.empty() } val remoteReceiver = remoteReceiverProvider.get() - remoteReceiver.addCallback(traceListener) + remoteReceiver.addCallback(traceListener.create("remoteReceiver")) return Optional.of(remoteReceiver) } @Provides @UseReceivingFilter fun useReceivingFilter(): Boolean = true + + @Provides + @UnfoldMain + fun provideMainRotationChangeProvider( + rotationChangeProviderFactory: RotationChangeProvider.Factory, + @UnfoldMain mainHandler: Handler, + ): RotationChangeProvider { + return rotationChangeProviderFactory.create(mainHandler) + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt index c3a6cf035d09..31b7ccca49ac 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt @@ -22,12 +22,12 @@ import android.hardware.SensorManager import android.hardware.display.DisplayManager import android.os.Handler import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.RotationChangeProvider -import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix @@ -63,13 +63,12 @@ interface UnfoldSharedComponent { @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor, @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String, @BindsInstance displayManager: DisplayManager, - @BindsInstance contentResolver: ContentResolver = context.contentResolver + @BindsInstance @UnfoldBg bgHandler: Handler, + @BindsInstance contentResolver: ContentResolver = context.contentResolver, ): UnfoldSharedComponent } val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider> - val hingeAngleProvider: HingeAngleProvider - val rotationChangeProvider: RotationChangeProvider } /** @@ -94,7 +93,8 @@ interface RemoteUnfoldSharedComponent { } val remoteTransitionProgress: Optional<RemoteUnfoldTransitionReceiver> - val rotationChangeProvider: RotationChangeProvider + + @UnfoldMain fun getRotationChangeProvider(): RotationChangeProvider } /** 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 7473ca6a6486..f7fb01465a40 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt @@ -16,14 +16,20 @@ 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 import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateRepository import com.android.systemui.unfold.updates.FoldStateRepositoryImpl +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider @@ -32,22 +38,27 @@ 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 = [UnfoldSharedInternalModule::class]) +@Module( + includes = + [ + UnfoldFlagsModule::class, + UnfoldSharedInternalModule::class, + UnfoldRotationProviderInternalModule::class, + HingeAngleProviderInternalModule::class, + FoldStateProviderModule::class, + ] +) class UnfoldSharedModule { @Provides @Singleton - fun provideFoldStateProvider( - deviceFoldStateProvider: DeviceFoldStateProvider - ): FoldStateProvider = deviceFoldStateProvider - - @Provides - @Singleton fun unfoldKeyguardVisibilityProvider( impl: UnfoldKeyguardVisibilityManagerImpl ): UnfoldKeyguardVisibilityProvider = impl @@ -60,9 +71,17 @@ class UnfoldSharedModule { @Provides @Singleton - fun foldStateRepository( - impl: FoldStateRepositoryImpl - ): FoldStateRepository = impl + 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 } /** @@ -77,17 +96,86 @@ internal class UnfoldSharedInternalModule { fun unfoldTransitionProgressProvider( config: UnfoldTransitionConfig, scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, + tracingListener: ATraceLoggerTransitionProgressListener.Factory, + physicsBasedUnfoldTransitionProgressProvider: + PhysicsBasedUnfoldTransitionProgressProvider.Factory, + fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, + foldStateProvider: FoldStateProvider, + @UnfoldMain mainHandler: Handler, + mainThreadUnfoldTransitionProgressProviderFactory: + MainThreadUnfoldTransitionProgressProvider.Factory, + @UnfoldBg bgProvider: Provider<Optional<UnfoldTransitionProgressProvider>>, + @UnfoldBgProgressFlag unfoldBgProgressFlag: Optional<Boolean>, + ): Optional<UnfoldTransitionProgressProvider> { + 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 + @Singleton + @UnfoldBg + fun unfoldBgTransitionProgressProvider( + config: UnfoldTransitionConfig, + scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, + tracingListener: ATraceLoggerTransitionProgressListener.Factory, + physicsBasedUnfoldTransitionProgressProvider: + PhysicsBasedUnfoldTransitionProgressProvider.Factory, + fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, + @UnfoldBg bgFoldStateProvider: FoldStateProvider, + @UnfoldBg bgHandler: Handler, + ): Optional<UnfoldTransitionProgressProvider> { + return createOptionalUnfoldTransitionProgressProvider( + config = config, + scaleAwareProviderFactory = scaleAwareProviderFactory, + tracingListener = tracingListener.create("BgThread"), + physicsBasedUnfoldTransitionProgressProvider = + physicsBasedUnfoldTransitionProgressProvider, + fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider, + foldStateProvider = bgFoldStateProvider, + progressHandler = bgHandler, + ) + } + + private fun createOptionalUnfoldTransitionProgressProvider( + config: UnfoldTransitionConfig, + scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, tracingListener: ATraceLoggerTransitionProgressListener, physicsBasedUnfoldTransitionProgressProvider: - Provider<PhysicsBasedUnfoldTransitionProgressProvider>, + PhysicsBasedUnfoldTransitionProgressProvider.Factory, fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, + foldStateProvider: FoldStateProvider, + progressHandler: Handler, ): Optional<UnfoldTransitionProgressProvider> { if (!config.isEnabled) { return Optional.empty() } val baseProgressProvider = if (config.isHingeAngleEnabled) { - physicsBasedUnfoldTransitionProgressProvider.get() + physicsBasedUnfoldTransitionProgressProvider.create( + foldStateProvider, + progressHandler + ) } else { fixedTimingTransitionProgressProvider.get() } @@ -101,26 +189,105 @@ internal class UnfoldSharedInternalModule { } @Provides + @Singleton + fun provideProgressForwarder( + config: UnfoldTransitionConfig, + progressForwarder: Provider<UnfoldTransitionProgressForwarder> + ): Optional<UnfoldTransitionProgressForwarder> { + if (!config.isEnabled) { + return Optional.empty() + } + return Optional.of(progressForwarder.get()) + } +} + +/** + * Provides [FoldStateProvider]. The [UnfoldBg] annotated binding sends progress in the [UnfoldBg] + * handler. + */ +@Module +internal class FoldStateProviderModule { + @Provides + @Singleton + fun provideFoldStateProvider( + factory: DeviceFoldStateProvider.Factory, + @UnfoldMain hingeAngleProvider: HingeAngleProvider, + @UnfoldMain rotationChangeProvider: RotationChangeProvider, + @UnfoldMain mainHandler: Handler, + ): FoldStateProvider = + factory.create( + hingeAngleProvider, + rotationChangeProvider, + progressHandler = mainHandler + ) + + @Provides + @Singleton + @UnfoldBg + fun provideBgFoldStateProvider( + factory: DeviceFoldStateProvider.Factory, + @UnfoldBg hingeAngleProvider: HingeAngleProvider, + @UnfoldBg rotationChangeProvider: RotationChangeProvider, + @UnfoldBg bgHandler: Handler, + ): FoldStateProvider = + factory.create( + hingeAngleProvider, + rotationChangeProvider, + progressHandler = bgHandler + ) +} + +/** Provides bindings for both [UnfoldMain] and [UnfoldBg] [HingeAngleProvider]. */ +@Module +internal class HingeAngleProviderInternalModule { + @Provides + @UnfoldMain fun hingeAngleProvider( config: UnfoldTransitionConfig, - hingeAngleSensorProvider: Provider<HingeSensorAngleProvider> + @UnfoldMain handler: Handler, + hingeAngleSensorProvider: HingeSensorAngleProvider.Factory ): HingeAngleProvider { return if (config.isHingeAngleEnabled) { - hingeAngleSensorProvider.get() + hingeAngleSensorProvider.create(handler) } else { EmptyHingeAngleProvider } } @Provides - @Singleton - fun provideProgressForwarder( - config: UnfoldTransitionConfig, - progressForwarder: Provider<UnfoldTransitionProgressForwarder> - ): Optional<UnfoldTransitionProgressForwarder> { - if (!config.isEnabled) { - return Optional.empty() + @UnfoldBg + fun hingeAngleProviderBg( + config: UnfoldTransitionConfig, + @UnfoldBg handler: Handler, + hingeAngleSensorProvider: HingeSensorAngleProvider.Factory + ): HingeAngleProvider { + return if (config.isHingeAngleEnabled) { + hingeAngleSensorProvider.create(handler) + } else { + EmptyHingeAngleProvider } - return Optional.of(progressForwarder.get()) + } +} + +@Module +internal class UnfoldRotationProviderInternalModule { + @Provides + @Singleton + @UnfoldMain + fun provideRotationChangeProvider( + rotationChangeProviderFactory: RotationChangeProvider.Factory, + @UnfoldMain mainHandler: Handler, + ): RotationChangeProvider { + return rotationChangeProviderFactory.create(mainHandler) + } + + @Provides + @Singleton + @UnfoldBg + fun provideBgRotationChangeProvider( + rotationChangeProviderFactory: RotationChangeProvider.Factory, + @UnfoldBg bgHandler: Handler, + ): RotationChangeProvider { + return rotationChangeProviderFactory.create(bgHandler) } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 18399194434a..1cbaf3135c4d 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -48,6 +48,7 @@ fun createUnfoldSharedComponent( singleThreadBgExecutor: Executor, tracingTagPrefix: String, displayManager: DisplayManager, + bgHandler: Handler, ): UnfoldSharedComponent = DaggerUnfoldSharedComponent.factory() .create( @@ -62,6 +63,7 @@ fun createUnfoldSharedComponent( singleThreadBgExecutor, tracingTagPrefix, displayManager, + bgHandler, ) /** diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt new file mode 100644 index 000000000000..7cd441950517 --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt @@ -0,0 +1,20 @@ +/* + * 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.dagger + +import javax.inject.Qualifier + +/** Annotation for background computations related to unfold lib. */ +@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldBg 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/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index f8f168bd4dc1..907bf46fb981 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Context +import android.os.Handler import android.os.Trace import android.util.FloatProperty import android.util.Log @@ -38,13 +39,25 @@ import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener import com.android.systemui.unfold.updates.name -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject -/** Maps fold updates to unfold transition progress using DynamicAnimation. */ +/** + * Maps fold updates to unfold transition progress using DynamicAnimation. + * + * Note that all variable accesses must be done in the [Handler] provided in the constructor, that + * might be different than [mainHandler]. When a custom handler is provided, the [SpringAnimation] + * uses a scheduler different than the default one. + */ class PhysicsBasedUnfoldTransitionProgressProvider -@Inject -constructor(context: Context, private val foldStateProvider: FoldStateProvider) : - UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { +@AssistedInject +constructor( + context: Context, + private val schedulerFactory: UnfoldFrameCallbackScheduler.Factory, + @Assisted private val foldStateProvider: FoldStateProvider, + @Assisted private val progressHandler: Handler, +) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { private val emphasizedInterpolator = loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in) @@ -63,6 +76,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) private var transitionProgress: Float = 0.0f set(value) { + assertInProgressThread() if (isTransitionRunning) { listeners.forEach { it.onTransitionProgress(value) } } @@ -72,8 +86,14 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) private val listeners: MutableList<TransitionProgressListener> = mutableListOf() init { - foldStateProvider.addCallback(this) - foldStateProvider.start() + progressHandler.post { + // The scheduler needs to be created in the progress handler in order to get the correct + // choreographer and frame callbacks. This is because the choreographer can be get only + // as a thread local. + springAnimation.scheduler = schedulerFactory.create() + foldStateProvider.addCallback(this) + foldStateProvider.start() + } } override fun destroy() { @@ -81,6 +101,8 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } override fun onHingeAngleUpdate(angle: Float) { + assertInProgressThread() + if (!isTransitionRunning || isAnimatedCancelRunning) return val progress = saturate(angle / FINAL_HINGE_ANGLE_POSITION) springAnimation.animateToFinalPosition(progress) @@ -90,6 +112,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) if (amount < low) low else if (amount > high) high else amount override fun onFoldUpdate(@FoldUpdate update: Int) { + assertInProgressThread() when (update) { FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> { @@ -148,6 +171,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } private fun cancelTransition(endValue: Float, animate: Boolean) { + assertInProgressThread() if (isTransitionRunning && animate) { if (endValue == 1.0f && !isAnimatedCancelRunning) { listeners.forEach { it.onTransitionFinishing() } @@ -165,7 +189,6 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) isAnimatedCancelRunning = false isTransitionRunning = false springAnimation.cancel() - cannedAnimator?.removeAllListeners() cannedAnimator?.cancel() cannedAnimator = null @@ -182,7 +205,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) animation: DynamicAnimation<out DynamicAnimation<*>>, canceled: Boolean, value: Float, - velocity: Float + velocity: Float, ) { if (isAnimatedCancelRunning) { cancelTransition(value, animate = false) @@ -202,6 +225,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } private fun startTransition(startValue: Float) { + assertInProgressThread() if (!isTransitionRunning) onStartTransition() springAnimation.apply { @@ -221,14 +245,16 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } override fun addCallback(listener: TransitionProgressListener) { - listeners.add(listener) + progressHandler.post { listeners.add(listener) } } override fun removeCallback(listener: TransitionProgressListener) { - listeners.remove(listener) + progressHandler.post { listeners.remove(listener) } } private fun startCannedCancelAnimation() { + assertInProgressThread() + cannedAnimator?.cancel() cannedAnimator = null @@ -264,7 +290,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) override fun setValue( provider: PhysicsBasedUnfoldTransitionProgressProvider, - value: Float + value: Float, ) { provider.transitionProgress = value } @@ -272,6 +298,25 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float = provider.transitionProgress } + + private fun assertInProgressThread() { + check(progressHandler.looper.isCurrentThread) { + val progressThread = progressHandler.looper.thread + val thisThread = Thread.currentThread() + """should be called from the progress thread. + progressThread=$progressThread tid=${progressThread.id} + Thread.currentThread()=$thisThread tid=${thisThread.id}""" + .trimMargin() + } + } + + @AssistedFactory + interface Factory { + fun create( + foldStateProvider: FoldStateProvider, + handler: Handler, + ): PhysicsBasedUnfoldTransitionProgressProvider + } } private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt new file mode 100644 index 000000000000..1dffd84f8242 --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt @@ -0,0 +1,59 @@ +/* + * 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.view.Choreographer +import androidx.dynamicanimation.animation.FrameCallbackScheduler +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** + * Scheduler that posts animation progresses on a thread different than the ui one. + * + * The following is taken from [AnimationHandler.FrameCallbackScheduler16]. It is extracted here as + * there are no guarantees which implementation the [DynamicAnimation] class would use otherwise. + * This allows classes using [DynamicAnimation] to be created in any thread, but still use the + * scheduler for a specific thread. + * + * Technically the [AssistedInject] is not needed: it's just to have a nicer factory with a + * documentation snippet instead of using a plain dagger provider. + */ +class UnfoldFrameCallbackScheduler @AssistedInject constructor() : FrameCallbackScheduler { + + private val choreographer = Choreographer.getInstance() + private val looper = + Looper.myLooper() ?: error("This should be created in a thread with a looper.") + + override fun postFrameCallback(frameCallback: Runnable) { + choreographer.postFrameCallback { frameCallback.run() } + } + + override fun isCurrentThread(): Boolean { + return looper.isCurrentThread + } + + @AssistedFactory + interface Factory { + /** + * Creates a [FrameCallbackScheduler] that uses [Choreographer] to post frame callbacks. + * + * Note that the choreographer used depends on the thread this [create] is called on, as it + * is get from a thread static attribute. + */ + fun create(): UnfoldFrameCallbackScheduler + } +} diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 003013e18583..77f637bb8ba1 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -23,37 +23,34 @@ import androidx.annotation.VisibleForTesting import androidx.core.util.Consumer import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP import com.android.systemui.unfold.config.UnfoldTransitionConfig -import com.android.systemui.unfold.dagger.UnfoldMain -import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate -import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener -import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor -import javax.inject.Inject class DeviceFoldStateProvider -@Inject +@AssistedInject constructor( config: UnfoldTransitionConfig, - private val hingeAngleProvider: HingeAngleProvider, + private val context: Context, private val screenStatusProvider: ScreenStatusProvider, - private val foldProvider: FoldProvider, private val activityTypeProvider: CurrentActivityTypeProvider, private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider, - private val rotationChangeProvider: RotationChangeProvider, - private val context: Context, - @UnfoldMain private val mainExecutor: Executor, - @UnfoldMain private val handler: Handler + private val foldProvider: FoldProvider, + @Assisted private val hingeAngleProvider: HingeAngleProvider, + @Assisted private val rotationChangeProvider: RotationChangeProvider, + @Assisted private val progressHandler: Handler, ) : FoldStateProvider { + private val outputListeners = CopyOnWriteArrayList<FoldStateProvider.FoldUpdatesListener>() - private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() - - @FoldUpdate private var lastFoldUpdate: Int? = null + @FoldStateProvider.FoldUpdate private var lastFoldUpdate: Int? = null @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f @@ -61,11 +58,9 @@ constructor( private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() private val foldStateListener = FoldStateListener() - private val mainLooper = handler.looper private val timeoutRunnable = Runnable { cancelAnimation() } - private val rotationListener = RotationListener { - if (isTransitionInProgress) cancelAnimation() - } + private val rotationListener = FoldRotationListener() + private val progressExecutor = Executor { progressHandler.post(it) } /** * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a @@ -80,9 +75,9 @@ constructor( private var isStarted = false override fun start() { - assertMainThread() if (isStarted) return - foldProvider.registerCallback(foldStateListener, mainExecutor) + foldProvider.registerCallback(foldStateListener, progressExecutor) + // TODO(b/277879146): get callbacks in the background screenStatusProvider.addCallback(screenListener) hingeAngleProvider.addCallback(hingeAngleListener) rotationChangeProvider.addCallback(rotationListener) @@ -91,7 +86,6 @@ constructor( } override fun stop() { - assertMainThread() screenStatusProvider.removeCallback(screenListener) foldProvider.unregisterCallback(foldStateListener) hingeAngleProvider.removeCallback(hingeAngleListener) @@ -101,11 +95,11 @@ constructor( isStarted = false } - override fun addCallback(listener: FoldUpdatesListener) { + override fun addCallback(listener: FoldStateProvider.FoldUpdatesListener) { outputListeners.add(listener) } - override fun removeCallback(listener: FoldUpdatesListener) { + override fun removeCallback(listener: FoldStateProvider.FoldUpdatesListener) { outputListeners.remove(listener) } @@ -121,6 +115,7 @@ constructor( lastFoldUpdate == FOLD_UPDATE_START_CLOSING private fun onHingeAngle(angle: Float) { + assertInProgressThread() if (DEBUG) { Log.d( TAG, @@ -131,14 +126,14 @@ constructor( } val currentDirection = - if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING + if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING if (isTransitionInProgress && currentDirection != lastFoldUpdate) { lastHingeAngleBeforeTransition = lastHingeAngle } val isClosing = angle < lastHingeAngleBeforeTransition val transitionUpdate = - if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING + if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING val angleChangeSurpassedThreshold = Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES @@ -150,12 +145,12 @@ constructor( angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle eventNotAlreadyDispatched && // we haven't sent transition event already !isFullyOpened && // do not send transition event if we are in fully opened hinge - // angle range as closing threshold could overlap this range + // angle range as closing threshold could overlap this range screenAvailableEventSent && // do not send transition event if we are still in the - // process of turning on the inner display + // process of turning on the inner display isClosingThresholdMet(angle) && // hinge angle is below certain threshold. isOnLargeScreen // Avoids sending closing event when on small screen. - // Start event is sent regardless due to hall sensor. + // Start event is sent regardless due to hall sensor. ) { notifyFoldUpdate(transitionUpdate, lastHingeAngle) } @@ -202,6 +197,7 @@ constructor( private inner class FoldStateListener : FoldProvider.FoldCallback { override fun onFoldUpdated(isFolded: Boolean) { + assertInProgressThread() this@DeviceFoldStateProvider.isFolded = isFolded lastHingeAngle = FULLY_CLOSED_DEGREES @@ -218,7 +214,14 @@ constructor( } } - private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) { + private inner class FoldRotationListener : RotationChangeProvider.RotationListener { + override fun onRotationChanged(newRotation: Int) { + assertInProgressThread() + if (isTransitionInProgress) cancelAnimation() + } + } + + private fun notifyFoldUpdate(@FoldStateProvider.FoldUpdate update: Int, angle: Float) { if (DEBUG) { Log.d(TAG, update.name()) } @@ -236,11 +239,11 @@ constructor( if (isTransitionInProgress) { cancelTimeout() } - handler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong()) + progressHandler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong()) } private fun cancelTimeout() { - handler.removeCallbacks(timeoutRunnable) + progressHandler.removeCallbacks(timeoutRunnable) } private fun cancelAnimation(): Unit = @@ -249,42 +252,61 @@ constructor( private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { override fun onScreenTurnedOn() { - // Trigger this event only if we are unfolded and this is the first screen - // turned on event since unfold started. This prevents running the animation when - // turning on the internal display using the power button. - // Initially isUnfoldHandled is true so it will be reset to false *only* when we - // receive 'folded' event. If SystemUI started when device is already folded it will - // still receive 'folded' event on startup. - if (!isFolded && !isUnfoldHandled) { - outputListeners.forEach { it.onUnfoldedScreenAvailable() } - isUnfoldHandled = true + executeInProgressThread { + // Trigger this event only if we are unfolded and this is the first screen + // turned on event since unfold started. This prevents running the animation when + // turning on the internal display using the power button. + // Initially isUnfoldHandled is true so it will be reset to false *only* when we + // receive 'folded' event. If SystemUI started when device is already folded it will + // still receive 'folded' event on startup. + if (!isFolded && !isUnfoldHandled) { + outputListeners.forEach { it.onUnfoldedScreenAvailable() } + isUnfoldHandled = true + } } } override fun markScreenAsTurnedOn() { - if (!isFolded) { - isUnfoldHandled = true + executeInProgressThread { + if (!isFolded) { + isUnfoldHandled = true + } } } override fun onScreenTurningOn() { - isScreenOn = true - updateHingeAngleProviderState() + executeInProgressThread { + isScreenOn = true + updateHingeAngleProviderState() + } } override fun onScreenTurningOff() { - isScreenOn = false - updateHingeAngleProviderState() + executeInProgressThread { + isScreenOn = false + updateHingeAngleProviderState() + } + } + + /** + * Needed just for compatibility while not all data sources are providing data in the + * background. + * + * TODO(b/277879146): Remove once ScreeStatusProvider provides in the background. + */ + private fun executeInProgressThread(f: () -> Unit) { + progressHandler.post { f() } } } private fun isOnLargeScreen(): Boolean { - return context.resources.configuration.smallestScreenWidthDp > - INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + return context.resources.configuration.smallestScreenWidthDp > + INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP } /** While the screen is off or the device is folded, hinge angle updates are not needed. */ private fun updateHingeAngleProviderState() { + assertInProgressThread() if (isScreenOn && !isFolded) { hingeAngleProvider.start() } else { @@ -294,20 +316,34 @@ constructor( private inner class HingeAngleListener : Consumer<Float> { override fun accept(angle: Float) { + assertInProgressThread() onHingeAngle(angle) } } - private fun assertMainThread() { - check(mainLooper.isCurrentThread) { - ("should be called from the main thread." + - " sMainLooper.threadName=" + mainLooper.thread.name + - " Thread.currentThread()=" + Thread.currentThread().name) + private fun assertInProgressThread() { + check(progressHandler.looper.isCurrentThread) { + val progressThread = progressHandler.looper.thread + val thisThread = Thread.currentThread() + """should be called from the progress thread. + progressThread=$progressThread tid=${progressThread.id} + Thread.currentThread()=$thisThread tid=${thisThread.id}""" + .trimMargin() } } + + @AssistedFactory + interface Factory { + /** Creates a [DeviceFoldStateProvider] using the provided dependencies. */ + fun create( + hingeAngleProvider: HingeAngleProvider, + rotationChangeProvider: RotationChangeProvider, + progressHandler: Handler, + ): DeviceFoldStateProvider + } } -fun @receiver:FoldUpdate Int.name() = +fun @receiver:FoldStateProvider.FoldUpdate Int.name() = when (this) { FOLD_UPDATE_START_OPENING -> "START_OPENING" FOLD_UPDATE_START_CLOSING -> "START_CLOSING" diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index ce8f1a178d05..82ea362e8049 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -20,20 +20,21 @@ import android.content.Context import android.hardware.display.DisplayManager import android.os.Handler import android.os.RemoteException -import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.util.CallbackController -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject /** - * Allows to subscribe to rotation changes. Updates are provided for the display associated - * to [context]. + * Allows to subscribe to rotation changes. Updates are provided for the display associated to + * [context]. */ class RotationChangeProvider -@Inject +@AssistedInject constructor( private val displayManager: DisplayManager, private val context: Context, - @UnfoldMain private val mainHandler: Handler, + @Assisted private val handler: Handler, ) : CallbackController<RotationChangeProvider.RotationListener> { private val listeners = mutableListOf<RotationListener>() @@ -42,7 +43,7 @@ constructor( private var lastRotation: Int? = null override fun addCallback(listener: RotationListener) { - mainHandler.post { + handler.post { if (listeners.isEmpty()) { subscribeToRotation() } @@ -51,7 +52,7 @@ constructor( } override fun removeCallback(listener: RotationListener) { - mainHandler.post { + handler.post { listeners -= listener if (listeners.isEmpty()) { unsubscribeToRotation() @@ -62,7 +63,7 @@ constructor( private fun subscribeToRotation() { try { - displayManager.registerDisplayListener(displayListener, mainHandler) + displayManager.registerDisplayListener(displayListener, handler) } catch (e: RemoteException) { throw e.rethrowFromSystemServer() } @@ -100,4 +101,10 @@ constructor( override fun onDisplayRemoved(displayId: Int) {} } + + @AssistedFactory + interface Factory { + /** Creates a new [RotationChangeProvider] that provides updated using [handler]. */ + fun create(handler: Handler): RotationChangeProvider + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt index 89fb12e313ec..14c4cc0aeecc 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt @@ -18,21 +18,26 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import android.os.Handler import android.os.Trace import androidx.core.util.Consumer import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor -import javax.inject.Inject internal class HingeSensorAngleProvider -@Inject +@AssistedInject constructor( private val sensorManager: SensorManager, - @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor + @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor, + @Assisted private val listenerHandler: Handler, ) : HingeAngleProvider { private val sensorListener = HingeAngleSensorListener() - private val listeners: MutableList<Consumer<Float>> = arrayListOf() + private val listeners: MutableList<Consumer<Float>> = CopyOnWriteArrayList() var started = false override fun start() { @@ -43,7 +48,8 @@ constructor( sensorManager.registerListener( sensorListener, sensor, - SensorManager.SENSOR_DELAY_FASTEST + SensorManager.SENSOR_DELAY_FASTEST, + listenerHandler ) Trace.endSection() @@ -75,4 +81,10 @@ constructor( listeners.forEach { it.accept(event.values[0]) } } } + + @AssistedFactory + interface Factory { + /** Creates an [HingeSensorAngleProvider] that sends updates using [handler]. */ + fun create(handler: Handler): HingeSensorAngleProvider + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt index d8bc01804f14..a31896a96a2b 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt @@ -16,7 +16,9 @@ package com.android.systemui.unfold.util import android.os.Trace import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import javax.inject.Qualifier /** @@ -26,11 +28,11 @@ import javax.inject.Qualifier * for each fold/unfold: in (1) systemui and (2) launcher process. */ class ATraceLoggerTransitionProgressListener -@Inject -internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) : +@AssistedInject +internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String, @Assisted details: String) : TransitionProgressListener { - private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME" + private val traceName = "$tracePrefix$details#$UNFOLD_TRANSITION_TRACE_NAME" override fun onTransitionStarted() { Trace.beginAsyncSection(traceName, /* cookie= */ 0) @@ -43,6 +45,12 @@ internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) : override fun onTransitionProgress(progress: Float) { Trace.setCounter(traceName, (progress * 100).toLong()) } + + @AssistedFactory + interface Factory { + /** Creates an [ATraceLoggerTransitionProgressListener] with [details] in the track name. */ + fun create(details: String): ATraceLoggerTransitionProgressListener + } } private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress" 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/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/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index fc565111dbe8..23c008eb7f67 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.LocusId; import android.content.res.Configuration; import android.os.IBinder; +import android.os.PersistableBundle; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -134,6 +135,18 @@ public abstract class UsageStatsManagerInternal { @Nullable LocusId locusId, @NonNull IBinder appToken); /** + * Report a user interaction event to UsageStatsManager + * + * @param pkgName The package for which this user interaction event occurred. + * @param userId The user id to which component belongs to. + * @param extras The extra details about this user interaction event. + * {@link UsageEvents.Event#USER_INTERACTION} + * {@link UsageStatsManager#reportUserInteraction(String, int, PersistableBundle)} + */ + public abstract void reportUserInteractionEvent(@NonNull String pkgName, @UserIdInt int userId, + @NonNull PersistableBundle extras); + + /** * Prepares the UsageStatsService for shutdown. */ public abstract void prepareShutdown(); 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 36356bd95128..1928780fa090 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -120,12 +120,15 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.AggregatedPowerStatsConfig; import com.android.server.power.stats.BatteryExternalStatsWorker; +import com.android.server.power.stats.BatteryStatsDumpHelperImpl; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor; import com.android.server.power.stats.PowerStatsAggregator; +import com.android.server.power.stats.PowerStatsExporter; import com.android.server.power.stats.PowerStatsScheduler; import com.android.server.power.stats.PowerStatsStore; +import com.android.server.power.stats.PowerStatsUidResolver; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.server.power.stats.wakeups.CpuWakeupStats; @@ -181,6 +184,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub private final BatteryExternalStatsWorker mWorker; private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; private final AtomicFile mConfigFile; + private final BatteryStats.BatteryStatsDumpHelper mDumpHelper; + private final PowerStatsUidResolver mPowerStatsUidResolver; private volatile boolean mMonitorEnabled = true; @@ -408,9 +413,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge) .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu) .build(); + mPowerStatsUidResolver = new PowerStatsUidResolver(); mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies); + mCpuScalingPolicies, mPowerStatsUidResolver); mWorker = new BatteryExternalStatsWorker(context, mStats); mStats.setExternalStatsSyncLocked(mWorker); mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger( @@ -419,8 +425,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig(); mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, - mPowerStatsStore); mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig, mStats.getHistory()); final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( @@ -429,7 +433,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.integer.config_powerStatsAggregationPeriod); mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore, - Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider); + Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats); + PowerStatsExporter powerStatsExporter = + new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, + powerStatsExporter, mPowerProfile, mCpuScalingPolicies, + mPowerStatsStore, Clock.SYSTEM_CLOCK); + mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore); + mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider); mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler); mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); } @@ -469,9 +480,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub } public void systemServicesReady() { + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(Flags.streamlinedBatteryStats()); + mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); - mWorker.systemServicesReady(); final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); @@ -775,25 +787,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub } void addIsolatedUid(final int isolatedUid, final int appUid) { - synchronized (mLock) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - mHandler.post(() -> { - synchronized (mStats) { - mStats.addIsolatedUidLocked(isolatedUid, appUid, elapsedRealtime, uptime); - } - }); - } + mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, appUid); + FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, + FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED); } void removeIsolatedUid(final int isolatedUid, final int appUid) { - synchronized (mLock) { - mHandler.post(() -> { - synchronized (mStats) { - mStats.scheduleRemoveIsolatedUidLocked(isolatedUid, appUid); - } - }); - } + mPowerStatsUidResolver.noteIsolatedUidRemoved(isolatedUid, appUid); + FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, isolatedUid, + FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED); } void noteProcessStart(final String name, final int uid) { @@ -877,12 +879,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub awaitCompletion(); - if (mBatteryUsageStatsProvider.shouldUpdateStats(queries, + if (BatteryUsageStatsProvider.shouldUpdateStats(queries, + SystemClock.elapsedRealtime(), mWorker.getLastCollectionTimeStamp())) { syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL); } - return mBatteryUsageStatsProvider.getBatteryUsageStats(queries); + synchronized (mStats) { + return mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, queries); + } } /** Register callbacks for statsd pulled atoms. */ @@ -2723,7 +2728,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mStats) { mStats.prepareForDumpLocked(); BatteryUsageStats batteryUsageStats = - mBatteryUsageStatsProvider.getBatteryUsageStats(query); + mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query); if (proto) { batteryUsageStats.dumpToProto(fd); } else { @@ -3008,11 +3013,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies); + mCpuScalingPolicies, new PowerStatsUidResolver()); checkinStats.readSummaryFromParcel(in); in.recycle(); - checkinStats.dumpProtoLocked( - mContext, fd, apps, flags, historyStart); + checkinStats.dumpProtoLocked(mContext, fd, apps, flags, + historyStart, mDumpHelper); mStats.mCheckinFile.delete(); return; } @@ -3026,7 +3031,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid()); awaitCompletion(); synchronized (mStats) { - mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart); + mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart, mDumpHelper); if (writeData) { mStats.writeAsyncLocked(); } @@ -3050,11 +3055,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies); + mCpuScalingPolicies, new PowerStatsUidResolver()); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpCheckin(mContext, pw, apps, flags, - historyStart); + historyStart, mDumpHelper); mStats.mCheckinFile.delete(); return; } @@ -3067,7 +3072,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } if (DBG) Slog.d(TAG, "begin dumpCheckin from UID " + Binder.getCallingUid()); awaitCompletion(); - mStats.dumpCheckin(mContext, pw, apps, flags, historyStart); + mStats.dumpCheckin(mContext, pw, apps, flags, historyStart, mDumpHelper); if (writeData) { mStats.writeAsyncLocked(); } @@ -3076,7 +3081,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (DBG) Slog.d(TAG, "begin dump from UID " + Binder.getCallingUid()); awaitCompletion(); - mStats.dump(mContext, pw, flags, reqUid, historyStart); + mStats.dump(mContext, pw, flags, reqUid, historyStart, mDumpHelper); if (writeData) { mStats.writeAsyncLocked(); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cb2b5fbe7a5a..4ff34b1d7faa 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -34,7 +34,7 @@ import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.killProcessQuiet; import static android.os.Process.startWebView; -import static android.system.OsConstants.*; +import static android.system.OsConstants.EAGAIN; import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; @@ -133,7 +133,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ProcessMap; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.MemInfoReader; import com.android.server.AppStateTracker; import com.android.server.LocalServices; @@ -3299,8 +3298,6 @@ public final class ProcessList { // about the process state of the isolated UID *before* it is registered with the // owning application. mService.mBatteryStatsService.addIsolatedUid(uid, info.uid); - FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, info.uid, uid, - FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED); } final ProcessRecord r = new ProcessRecord(mService, info, proc, uid, sdkSandboxClientAppPackage, 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/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index 0ee7d9cdd3d4..091696737dcf 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java @@ -20,6 +20,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT; +import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -150,7 +151,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { synchronized (mLock) { SparseIntArray opModes = mUidModes.get(uid, null); if (opModes == null) { @@ -176,7 +177,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public int getUidMode(int uid, int op) { + public int getUidMode(int uid, String persistentDeviceId, int op) { synchronized (mLock) { SparseIntArray opModes = mUidModes.get(uid, null); if (opModes == null) { @@ -187,7 +188,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public boolean setUidMode(int uid, int op, int mode) { + public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) { final int defaultMode = AppOpsManager.opToDefaultMode(op); List<AppOpsModeChangedListener> listenersCopy; synchronized (mLock) { @@ -329,7 +330,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { SparseBooleanArray result = new SparseBooleanArray(); synchronized (mLock) { SparseIntArray modes = mUidModes.get(uid); @@ -606,9 +607,17 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface for (final String pkg : packagesDeclaringPermission) { for (int userId : userIds) { final int uid = pmi.getPackageUid(pkg, 0, userId); - final int oldMode = getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); + final int oldMode = + getUidMode( + uid, + PERSISTENT_DEVICE_ID_DEFAULT, + OP_SCHEDULE_EXACT_ALARM); if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) { - setUidMode(uid, OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED); + setUidMode( + uid, + PERSISTENT_DEVICE_ID_DEFAULT, + OP_SCHEDULE_EXACT_ALARM, + MODE_ALLOWED); } } // This appop is meant to be controlled at a uid level. So we leave package modes as @@ -641,7 +650,10 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface final int flags = permissionManager.getPermissionFlags(pkg, permissionName, UserHandle.of(userId)); if ((flags & PackageManager.FLAG_PERMISSION_USER_SET) == 0) { - setUidMode(uid, OP_USE_FULL_SCREEN_INTENT, + setUidMode( + uid, + PERSISTENT_DEVICE_ID_DEFAULT, + OP_USE_FULL_SCREEN_INTENT, AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT)); } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java index f6e6bc0be8fa..f056f6b10b0d 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java @@ -59,8 +59,9 @@ public interface AppOpsCheckingServiceInterface { * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid. * Returns an empty SparseIntArray if nothing is set. * @param uid for which we need the app-ops and their modes. + * @param persistentDeviceId device for which we need the app-ops and their modes */ - SparseIntArray getNonDefaultUidModes(int uid); + SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId); /** * Returns a copy of non-default app-ops with op as keys and their modes as values for a package @@ -75,20 +76,22 @@ public interface AppOpsCheckingServiceInterface { * Returns the app-op mode for a particular app-op of a uid. * Returns default op mode if the op mode for particular uid and op is not set. * @param uid user id for which we need the mode. + * @param persistentDeviceId device for which we need the mode * @param op app-op for which we need the mode. * @return mode of the app-op. */ - int getUidMode(int uid, int op); + int getUidMode(int uid, String persistentDeviceId, int op); /** * Set the app-op mode for a particular uid and op. * The mode is not set if the mode is the same as the default mode for the op. * @param uid user id for which we want to set the mode. + * @param persistentDeviceId device for which we want to set the mode. * @param op app-op for which we want to set the mode. * @param mode mode for the app-op. * @return true if op mode is changed. */ - boolean setUidMode(int uid, int op, @Mode int mode); + boolean setUidMode(int uid, String persistentDeviceId, int op, @Mode int mode); /** * Gets the app-op mode for a particular package. @@ -130,10 +133,11 @@ public interface AppOpsCheckingServiceInterface { /** * @param uid UID to query foreground ops for. + * @param persistentDeviceId device to query foreground ops for * @return SparseBooleanArray where the keys are the op codes for which their modes are * MODE_FOREGROUND for the passed UID. */ - SparseBooleanArray getForegroundOps(int uid); + SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId); /** * diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java index ccdf3a5baa7b..f6da166d9a34 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java @@ -60,9 +60,9 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { Log.i(LOG_TAG, "getNonDefaultUidModes(uid = " + uid + ")"); - return mService.getNonDefaultUidModes(uid); + return mService.getNonDefaultUidModes(uid, persistentDeviceId); } @Override @@ -73,15 +73,15 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public int getUidMode(int uid, int op) { + public int getUidMode(int uid, String persistentDeviceId, int op) { Log.i(LOG_TAG, "getUidMode(uid = " + uid + ", op = " + op + ")"); - return mService.getUidMode(uid, op); + return mService.getUidMode(uid, persistentDeviceId, op); } @Override - public boolean setUidMode(int uid, int op, int mode) { + public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) { Log.i(LOG_TAG, "setUidMode(uid = " + uid + ", op = " + op + ", mode = " + mode + ")"); - return mService.setUidMode(uid, op, mode); + return mService.setUidMode(uid, persistentDeviceId, op, mode); } @Override @@ -117,9 +117,9 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { Log.i(LOG_TAG, "getForegroundOps(uid = " + uid + ")"); - return mService.getForegroundOps(uid); + return mService.getForegroundOps(uid, persistentDeviceId); } @Override diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java index c3a02a84a277..55cf7ed80483 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java @@ -81,11 +81,11 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes"); try { - return mService.getNonDefaultUidModes(uid); + return mService.getNonDefaultUidModes(uid, persistentDeviceId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -103,20 +103,21 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public int getUidMode(int uid, int op) { + public int getUidMode(int uid, String persistentDeviceId, int op) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode"); try { - return mService.getUidMode(uid, op); + return mService.getUidMode(uid, persistentDeviceId, op); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) { + public boolean setUidMode( + int uid, String persistentDeviceId, int op, @AppOpsManager.Mode int mode) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode"); try { - return mService.setUidMode(uid, op, mode); + return mService.setUidMode(uid, persistentDeviceId, op, mode); } finally { Trace.traceEnd(TRACE_TAG); } @@ -179,11 +180,11 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps"); try { - return mService.getForegroundOps(uid); + return mService.getForegroundOps(uid, persistentDeviceId); } finally { Trace.traceEnd(TRACE_TAG); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 14aab13bf638..344673793700 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -63,6 +63,7 @@ import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; +import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; @@ -1349,7 +1350,10 @@ public class AppOpsService extends IAppOpsService.Stub { SparseBooleanArray foregroundOps = new SparseBooleanArray(); - SparseBooleanArray uidForegroundOps = mAppOpsCheckingService.getForegroundOps(uid); + // TODO(b/299330771): Check uidForegroundOps for all devices. + SparseBooleanArray uidForegroundOps = + mAppOpsCheckingService.getForegroundOps( + uid, PERSISTENT_DEVICE_ID_DEFAULT); for (int i = 0; i < uidForegroundOps.size(); i++) { foregroundOps.put(uidForegroundOps.keyAt(i), true); } @@ -1369,10 +1373,16 @@ public class AppOpsService extends IAppOpsService.Stub { continue; } final int code = foregroundOps.keyAt(fgi); - - if (mAppOpsCheckingService.getUidMode(uidState.uid, code) + // TODO(b/299330771): Notify op changes for all relevant devices. + if (mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + code) != AppOpsManager.opToDefaultMode(code) - && mAppOpsCheckingService.getUidMode(uidState.uid, code) + && mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + code) == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, @@ -1489,7 +1499,11 @@ public class AppOpsService extends IAppOpsService.Stub { @Nullable private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState, @Nullable int[] ops) { - final SparseIntArray opModes = mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid); + // TODO(b/299330771): Make this methods device-aware, currently it represents only the + // primary device. + final SparseIntArray opModes = + mAppOpsCheckingService.getNonDefaultUidModes( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT); if (opModes == null) { return null; } @@ -1844,16 +1858,22 @@ public class AppOpsService extends IAppOpsService.Stub { uidState = new UidState(uid); mUidStates.put(uid, uidState); } - if (mAppOpsCheckingService.getUidMode(uidState.uid, code) + // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for runtime + // permissions is deprecated. + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code) != AppOpsManager.opToDefaultMode(code)) { - previousMode = mAppOpsCheckingService.getUidMode(uidState.uid, code); + previousMode = + mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code); } else { // doesn't look right but is legacy behavior. previousMode = MODE_DEFAULT; } mIgnoredCallback = permissionPolicyCallback; - if (!mAppOpsCheckingService.setUidMode(uidState.uid, code, mode)) { + if (!mAppOpsCheckingService.setUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code, mode)) { return; } if (mode != MODE_ERRORED && mode != previousMode) { @@ -2275,8 +2295,10 @@ public class AppOpsService extends IAppOpsService.Stub { boolean changed = false; for (int i = mUidStates.size() - 1; i >= 0; i--) { UidState uidState = mUidStates.valueAt(i); - - SparseIntArray opModes = mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid); + // TODO(b/299330771): Check non default modes for all devices. + SparseIntArray opModes = + mAppOpsCheckingService.getNonDefaultUidModes( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT); if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) { final int uidOpCount = opModes.size(); for (int j = uidOpCount - 1; j >= 0; j--) { @@ -2285,7 +2307,12 @@ public class AppOpsService extends IAppOpsService.Stub { int previousMode = opModes.valueAt(j); int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED : AppOpsManager.opToDefaultMode(code); - mAppOpsCheckingService.setUidMode(uidState.uid, code, newMode); + // TODO(b/299330771): Set mode for all necessary devices. + mAppOpsCheckingService.setUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + code, + newMode); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, previousMode, mOpModeWatchers.get(code)); @@ -2601,10 +2628,14 @@ public class AppOpsService extends IAppOpsService.Stub { } code = AppOpsManager.opToSwitch(code); UidState uidState = getUidStateLocked(uid, false); + // TODO(b/299330771): Check mode for the relevant device. if (uidState != null - && mAppOpsCheckingService.getUidMode(uidState.uid, code) + && mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code) != AppOpsManager.opToDefaultMode(code)) { - final int rawMode = mAppOpsCheckingService.getUidMode(uidState.uid, code); + final int rawMode = + mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code); return raw ? rawMode : uidState.evalMode(code, rawMode); } Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false); @@ -2851,13 +2882,19 @@ public class AppOpsService extends IAppOpsService.Stub { return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, packageName); } + // TODO(b/299330771): Check mode for the relevant device. // If there is a non-default per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. - if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode) + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { final int uidMode = uidState.evalMode( - code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)); + code, + mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + switchCode)); if (uidMode != AppOpsManager.MODE_ALLOWED) { if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " @@ -3396,13 +3433,19 @@ public class AppOpsService extends IAppOpsService.Stub { isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false); final int switchCode = AppOpsManager.opToSwitch(code); + // TODO(b/299330771): Check mode for the relevant device. // If there is a non-default per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. - if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode) + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { final int uidMode = uidState.evalMode( - code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)); + code, + mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + switchCode)); if (!shouldStartForMode(uidMode, startIfModeDefault)) { if (DEBUG) { Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " @@ -3511,13 +3554,19 @@ public class AppOpsService extends IAppOpsService.Stub { isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false); final int switchCode = AppOpsManager.opToSwitch(code); + // TODO(b/299330771): Check mode for the relevant device. // If there is a non-default mode per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. - if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode) + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { final int uidMode = uidState.evalMode( - code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)); + code, + mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + switchCode)); if (!shouldStartForMode(uidMode, startIfModeDefault)) { if (DEBUG) { Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " @@ -5664,8 +5713,10 @@ public class AppOpsService extends IAppOpsService.Stub { } for (int i=0; i<mUidStates.size(); i++) { UidState uidState = mUidStates.valueAt(i); + // TODO(b/299330771): Get modes for all devices. final SparseIntArray opModes = - mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid); + mAppOpsCheckingService.getNonDefaultUidModes( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT); final ArrayMap<String, Ops> pkgOps = uidState.pkgOps; if (dumpWatchers || dumpHistory) { diff --git a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java index 98e6476e9707..c9fa9e600ecc 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java @@ -65,9 +65,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { - SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid); - SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid); + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { + SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid, persistentDeviceId); + SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid, persistentDeviceId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getNonDefaultUidModes"); @@ -89,9 +89,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public int getUidMode(int uid, int op) { - int oldVal = mOldImplementation.getUidMode(uid, op); - int newVal = mNewImplementation.getUidMode(uid, op); + public int getUidMode(int uid, String persistentDeviceId, int op) { + int oldVal = mOldImplementation.getUidMode(uid, persistentDeviceId, op); + int newVal = mNewImplementation.getUidMode(uid, persistentDeviceId, op); if (oldVal != newVal) { signalImplDifference("getUidMode"); @@ -101,9 +101,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public boolean setUidMode(int uid, int op, int mode) { - boolean oldVal = mOldImplementation.setUidMode(uid, op, mode); - boolean newVal = mNewImplementation.setUidMode(uid, op, mode); + public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) { + boolean oldVal = mOldImplementation.setUidMode(uid, persistentDeviceId, op, mode); + boolean newVal = mNewImplementation.setUidMode(uid, persistentDeviceId, op, mode); if (oldVal != newVal) { signalImplDifference("setUidMode"); @@ -155,9 +155,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public SparseBooleanArray getForegroundOps(int uid) { - SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid); - SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid); + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { + SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid, persistentDeviceId); + SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid, persistentDeviceId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getForegroundOps"); 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/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 6f6bb45bad50..f7f76aaaee16 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -807,7 +807,7 @@ final class DefaultPermissionGrantPolicy { getDefaultSystemHandlerActivityPackage(pm, SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId), userId, MICROPHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, - NOTIFICATION_PERMISSIONS); + NOTIFICATION_PERMISSIONS, PHONE_PERMISSIONS); } // Voice recognition 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/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index aadd03b25428..894226cf32c9 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -33,6 +33,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -265,4 +266,11 @@ class AggregatedPowerStats { ipw.decreaseIndent(); } } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + dump(new IndentingPrintWriter(sw)); + return sw.toString(); + } } diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index f9d57e4c9042..a8eda3ca6a47 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -44,11 +44,8 @@ import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.power.EnergyConsumerStats; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; -import libcore.util.EmptyArray; - import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -128,9 +125,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat private boolean mUseLatestStates = true; @GuardedBy("this") - private final IntArray mUidsToRemove = new IntArray(); - - @GuardedBy("this") private Future<?> mWakelockChangesUpdate; @GuardedBy("this") @@ -260,7 +254,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat @Override public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) { - mUidsToRemove.add(uid); return scheduleSyncLocked("remove-uid", UPDATE_CPU); } @@ -459,7 +452,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat // Capture a snapshot of the state we are meant to process. final int updateFlags; final String reason; - final int[] uidsToRemove; final boolean onBattery; final boolean onBatteryScreenOff; final int screenState; @@ -468,7 +460,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat synchronized (BatteryExternalStatsWorker.this) { updateFlags = mUpdateFlags; reason = mCurrentReason; - uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT; onBattery = mOnBattery; onBatteryScreenOff = mOnBatteryScreenOff; screenState = mScreenState; @@ -476,7 +467,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat useLatestStates = mUseLatestStates; mUpdateFlags = 0; mCurrentReason = null; - mUidsToRemove.clear(); mCurrentFuture = null; mUseLatestStates = true; if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) { @@ -512,12 +502,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat // Clean up any UIDs if necessary. synchronized (mStats) { - for (int uid : uidsToRemove) { - FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid, - FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED); - mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(), - SystemClock.uptimeMillis()); - } mStats.clearPendingRemovedUidsLocked(); } } catch (Exception e) { diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java new file mode 100644 index 000000000000..ad146afe16e7 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java @@ -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.server.power.stats; + +import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; + +public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDumpHelper { + private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; + + public BatteryStatsDumpHelperImpl(BatteryUsageStatsProvider batteryUsageStatsProvider) { + mBatteryUsageStatsProvider = batteryUsageStatsProvider; + } + + @Override + public BatteryUsageStats getBatteryUsageStats(BatteryStats batteryStats, boolean detailed) { + BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0); + if (detailed) { + builder.includePowerModels().includeProcessStateData().includeVirtualUids(); + } + return mBatteryUsageStatsProvider.getBatteryUsageStats((BatteryStatsImpl) batteryStats, + builder.build()); + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index eb401043af03..0491c14e52bd 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -185,7 +185,7 @@ public class BatteryStatsImpl extends BatteryStats { // TODO: remove "tcp" from network methods, since we measure total stats. // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - public static final int VERSION = 213; + public static final int VERSION = 214; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -220,6 +220,8 @@ public class BatteryStatsImpl extends BatteryStats { public static final int RESET_REASON_FULL_CHARGE = 3; public static final int RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE = 4; public static final int RESET_REASON_PLUGGED_IN_FOR_LONG_DURATION = 5; + @NonNull + private final MonotonicClock mMonotonicClock; protected Clock mClock; @@ -393,19 +395,9 @@ public class BatteryStatsImpl extends BatteryStats { } } - /** - * Listener for the battery stats reset. - */ - public interface BatteryResetListener { - - /** - * Callback invoked immediately prior to resetting battery stats. - * @param resetReason One of the RESET_REASON_* constants. - */ - void prepareForBatteryStatsReset(int resetReason); - } - - private BatteryResetListener mBatteryResetListener; + private boolean mSaveBatteryUsageStatsOnReset; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private PowerStatsStore mPowerStatsStore; public interface BatteryCallback { public void batteryNeedsCpuUpdate(); @@ -787,13 +779,10 @@ public class BatteryStatsImpl extends BatteryStats { private BatteryCallback mCallback; /** - * Mapping isolated uids to the actual owning app uid. - */ - private final SparseIntArray mIsolatedUids = new SparseIntArray(); - /** - * Internal reference count of isolated uids. + * Mapping child uids to their parent uid. */ - private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray(); + @VisibleForTesting + protected final PowerStatsUidResolver mPowerStatsUidResolver; /** * The statistics we have collected organized by uids. @@ -874,6 +863,8 @@ public class BatteryStatsImpl extends BatteryStats { long mUptimeStartUs; long mRealtimeUs; long mRealtimeStartUs; + long mMonotonicStartTime; + long mMonotonicEndTime = MonotonicClock.UNDEFINED; int mWakeLockNesting; boolean mWakeLockImportant; @@ -1724,25 +1715,26 @@ public class BatteryStatsImpl extends BatteryStats { } @VisibleForTesting - public BatteryStatsImpl(Clock clock, File historyDirectory) { + public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler, + @NonNull PowerStatsUidResolver powerStatsUidResolver) { init(clock); mBatteryStatsConfig = new BatteryStatsConfig.Builder().build(); - mHandler = null; + mHandler = handler; + mPowerStatsUidResolver = powerStatsUidResolver; mConstants = new Constants(mHandler); mStartClockTimeMs = clock.currentTimeMillis(); mDailyFile = null; + mMonotonicClock = new MonotonicClock(0, mClock); if (historyDirectory == null) { mCheckinFile = null; mStatsFile = null; mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, - new MonotonicClock(0, mClock)); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } else { mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin")); mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin")); mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, - new MonotonicClock(0, mClock)); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } mPlatformIdleStateCallback = null; mEnergyConsumerRetriever = null; @@ -4278,92 +4270,51 @@ public class BatteryStatsImpl extends BatteryStats { } } - @GuardedBy("this") - public void addIsolatedUidLocked(int isolatedUid, int appUid) { - addIsolatedUidLocked(isolatedUid, appUid, - mClock.elapsedRealtime(), mClock.uptimeMillis()); + private void onIsolatedUidAdded(int isolatedUid, int parentUid) { + long realtime = mClock.elapsedRealtime(); + long uptime = mClock.uptimeMillis(); + synchronized (this) { + getUidStatsLocked(parentUid, realtime, uptime).addIsolatedUid(isolatedUid); + } } - @GuardedBy("this") - @SuppressWarnings("GuardedBy") // errorprone false positive on u.addIsolatedUid - public void addIsolatedUidLocked(int isolatedUid, int appUid, - long elapsedRealtimeMs, long uptimeMs) { - mIsolatedUids.put(isolatedUid, appUid); - mIsolatedUidRefCounts.put(isolatedUid, 1); - final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs); - u.addIsolatedUid(isolatedUid); + private void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) { + long realtime = mClock.elapsedRealtime(); + mPowerStatsUidResolver.retainIsolatedUid(isolatedUid); + synchronized (this) { + mPendingRemovedUids.add(new UidToRemove(isolatedUid, realtime)); + } + if (mExternalSync != null) { + mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid); + } } - /** - * Schedules a read of the latest cpu times before removing the isolated UID. - * @see #removeIsolatedUidLocked(int, int, int) - */ - public void scheduleRemoveIsolatedUidLocked(int isolatedUid, int appUid) { - int curUid = mIsolatedUids.get(isolatedUid, -1); - if (curUid == appUid) { - if (mExternalSync != null) { - mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid); - } + private void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) { + long realtime = mClock.elapsedRealtime(); + long uptime = mClock.uptimeMillis(); + synchronized (this) { + getUidStatsLocked(parentUid, realtime, uptime).removeIsolatedUid(isolatedUid); } } /** * Isolated uid should only be removed after all wakelocks associated with the uid are stopped * and the cpu time-in-state has been read one last time for the uid. - * - * @see #scheduleRemoveIsolatedUidLocked(int, int) - * - * @return true if the isolated uid is actually removed. */ @GuardedBy("this") - public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, - long uptimeMs) { - final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1; - if (refCount > 0) { - // Isolated uid is still being tracked - mIsolatedUidRefCounts.put(isolatedUid, refCount); - return false; - } - - final int idx = mIsolatedUids.indexOfKey(isolatedUid); - if (idx >= 0) { - final int ownerUid = mIsolatedUids.valueAt(idx); - final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs); - u.removeIsolatedUid(isolatedUid); - mIsolatedUids.removeAt(idx); - mIsolatedUidRefCounts.delete(isolatedUid); - } else { - Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")"); - } - mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs)); - - return true; - } - - /** - * Increment the ref count for an isolated uid. - * call #maybeRemoveIsolatedUidLocked to decrement. - */ - public void incrementIsolatedUidRefCount(int uid) { - final int refCount = mIsolatedUidRefCounts.get(uid, 0); - if (refCount <= 0) { - // Uid is not mapped or referenced - Slog.w(TAG, - "Attempted to increment ref counted of untracked isolated uid (" + uid + ")"); - return; - } - mIsolatedUidRefCounts.put(uid, refCount + 1); + public void releaseIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) { + mPowerStatsUidResolver.releaseIsolatedUid(isolatedUid); } private int mapUid(int uid) { if (Process.isSdkSandboxUid(uid)) { return Process.getAppUidForSdkSandboxUid(uid); } - return mapIsolatedUid(uid); + return mPowerStatsUidResolver.mapUid(uid); } private int mapIsolatedUid(int uid) { - return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid); + return mPowerStatsUidResolver.mapUid(uid); } @GuardedBy("this") @@ -4745,7 +4696,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mappedUid != uid) { // Prevent the isolated uid mapping from being removed while the wakelock is // being held. - incrementIsolatedUidRefCount(uid); + mPowerStatsUidResolver.retainIsolatedUid(uid); } if (mOnBatteryScreenOffTimeBase.isRunning()) { // We only update the cpu time when a wake lock is acquired if the screen is off. @@ -4825,7 +4776,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. - maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); + releaseIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); } } } @@ -4996,7 +4947,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mappedUid != uid) { // Prevent the isolated uid mapping from being removed while the wakelock is // being held. - incrementIsolatedUidRefCount(uid); + mPowerStatsUidResolver.retainIsolatedUid(uid); } } @@ -5048,7 +4999,7 @@ public class BatteryStatsImpl extends BatteryStats { historyName, mappedUid); if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. - maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); + releaseIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); } } @@ -7642,35 +7593,53 @@ public class BatteryStatsImpl extends BatteryStats { /** * Returns the names of custom power components. */ - @GuardedBy("this") @Override public @NonNull String[] getCustomEnergyConsumerNames() { - if (mEnergyConsumerStatsConfig == null) { - return new String[0]; - } - final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames(); - for (int i = 0; i < names.length; i++) { - if (TextUtils.isEmpty(names[i])) { - names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i; + synchronized (this) { + if (mEnergyConsumerStatsConfig == null) { + return new String[0]; } + final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames(); + for (int i = 0; i < names.length; i++) { + if (TextUtils.isEmpty(names[i])) { + names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i; + } + } + return names; } - return names; } - @GuardedBy("this") - @Override public long getStartClockTime() { - final long currentTimeMs = mClock.currentTimeMillis(); - if ((currentTimeMs > MILLISECONDS_IN_YEAR - && mStartClockTimeMs < (currentTimeMs - MILLISECONDS_IN_YEAR)) + @Override + public long getStartClockTime() { + synchronized (this) { + final long currentTimeMs = mClock.currentTimeMillis(); + if ((currentTimeMs > MILLISECONDS_IN_YEAR + && mStartClockTimeMs < (currentTimeMs - MILLISECONDS_IN_YEAR)) || (mStartClockTimeMs > currentTimeMs)) { - // If the start clock time has changed by more than a year, then presumably - // the previous time was completely bogus. So we are going to figure out a - // new time based on how much time has elapsed since we started counting. - mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(), - currentTimeMs); - adjustStartClockTime(currentTimeMs); + // If the start clock time has changed by more than a year, then presumably + // the previous time was completely bogus. So we are going to figure out a + // new time based on how much time has elapsed since we started counting. + mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(), + currentTimeMs); + adjustStartClockTime(currentTimeMs); + } + return mStartClockTimeMs; } - return mStartClockTimeMs; + } + + /** + * Returns the monotonic time when the BatteryStats session started. + */ + public long getMonotonicStartTime() { + return mMonotonicStartTime; + } + + /** + * Returns the monotonic time when the BatteryStats session ended, or + * {@link MonotonicClock#UNDEFINED} if the session is still ongoing. + */ + public long getMonotonicEndTime() { + return mMonotonicEndTime; } @Override public String getStartPlatformVersion() { @@ -8197,7 +8166,9 @@ public class BatteryStatsImpl extends BatteryStats { return mProportionalSystemServiceUsage; } - @GuardedBy("mBsi") + /** + * Adds isolated UID to the list of children. + */ public void addIsolatedUid(int isolatedUid) { if (mChildUids == null) { mChildUids = new SparseArray<>(); @@ -8207,6 +8178,9 @@ public class BatteryStatsImpl extends BatteryStats { mChildUids.put(isolatedUid, new ChildUid()); } + /** + * Removes isolated UID from the list of children. + */ public void removeIsolatedUid(int isolatedUid) { final int idx = mChildUids == null ? -1 : mChildUids.indexOfKey(isolatedUid); if (idx < 0) { @@ -10910,15 +10884,18 @@ public class BatteryStatsImpl extends BatteryStats { @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb, @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, - @NonNull CpuScalingPolicies cpuScalingPolicies) { + @NonNull CpuScalingPolicies cpuScalingPolicies, + @NonNull PowerStatsUidResolver powerStatsUidResolver) { init(clock); mBatteryStatsConfig = config; + mMonotonicClock = monotonicClock; mHandler = new MyHandler(handler.getLooper()); mConstants = new Constants(mHandler); mPowerProfile = powerProfile; mCpuScalingPolicies = cpuScalingPolicies; + mPowerStatsUidResolver = powerStatsUidResolver; initPowerProfile(); @@ -10927,17 +10904,17 @@ public class BatteryStatsImpl extends BatteryStats { mCheckinFile = null; mDailyFile = null; mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } else { mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin")); mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin")); mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml")); mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile, - () -> mBatteryVoltageMv, mHandler, + mPowerStatsUidResolver, () -> mBatteryVoltageMv, mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu()); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); @@ -10954,6 +10931,23 @@ public class BatteryStatsImpl extends BatteryStats { mEnergyConsumerRetriever = energyStatsCb; mUserInfoProvider = userInfoProvider; + mPowerStatsUidResolver.addListener(new PowerStatsUidResolver.Listener() { + @Override + public void onIsolatedUidAdded(int isolatedUid, int parentUid) { + BatteryStatsImpl.this.onIsolatedUidAdded(isolatedUid, parentUid); + } + + @Override + public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) { + BatteryStatsImpl.this.onBeforeIsolatedUidRemoved(isolatedUid, parentUid); + } + + @Override + public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) { + BatteryStatsImpl.this.onAfterIsolatedUidRemoved(isolatedUid, parentUid); + } + }); + // Notify statsd that the system is initially not in doze. mDeviceIdleMode = DEVICE_IDLE_MODE_OFF; FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode); @@ -11497,6 +11491,7 @@ public class BatteryStatsImpl extends BatteryStats { mUptimeUs = 0; mRealtimeStartUs = realtimeUs; mUptimeStartUs = uptimeUs; + mMonotonicStartTime = mMonotonicClock.monotonicTime(); } void initDischarge(long elapsedRealtimeUs) { @@ -11517,8 +11512,17 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.reset(false, elapsedRealtimeUs); } - public void setBatteryResetListener(BatteryResetListener batteryResetListener) { - mBatteryResetListener = batteryResetListener; + /** + * Associates the BatteryStatsImpl object with a BatteryUsageStatsProvider and PowerStatsStore + * to allow for a snapshot of battery usage stats to be taken and stored just before battery + * reset. + */ + public void saveBatteryUsageStatsOnReset( + @NonNull BatteryUsageStatsProvider batteryUsageStatsProvider, + @NonNull PowerStatsStore powerStatsStore) { + mSaveBatteryUsageStatsOnReset = true; + mBatteryUsageStatsProvider = batteryUsageStatsProvider; + mPowerStatsStore = powerStatsStore; } @GuardedBy("this") @@ -11557,9 +11561,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") private void resetAllStatsLocked(long uptimeMillis, long elapsedRealtimeMillis, int resetReason) { - if (mBatteryResetListener != null) { - mBatteryResetListener.prepareForBatteryStatsReset(resetReason); - } + saveBatteryUsageStatsOnReset(resetReason); final long uptimeUs = uptimeMillis * 1000; final long elapsedRealtimeUs = elapsedRealtimeMillis * 1000; @@ -11707,6 +11709,31 @@ public class BatteryStatsImpl extends BatteryStats { mHandler.sendEmptyMessage(MSG_REPORT_RESET_STATS); } + private void saveBatteryUsageStatsOnReset(int resetReason) { + if (!mSaveBatteryUsageStatsOnReset + || resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) { + return; + } + + final BatteryUsageStats batteryUsageStats; + synchronized (this) { + batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this, + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .includeProcessStateData() + .build()); + } + + // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end + // Once that change is made, we will be able to use the BatteryUsageStats' monotonic + // start time + long monotonicStartTime = + mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration(); + mHandler.post(() -> + mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats)); + } + @GuardedBy("this") private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) { for (int i=0; i<HistoryItem.EVENT_COUNT; i++) { @@ -15137,6 +15164,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mKernelSingleUidTimeReader != null) { mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid); } + mPowerStatsUidResolver.releaseUidsInRange(startUid, endUid); // Treat as one. We don't know how many uids there are in between. mNumUidsRemoved++; } else { @@ -15192,10 +15220,11 @@ public class BatteryStatsImpl extends BatteryStats { mShuttingDown = true; } - @GuardedBy("this") @Override public boolean isProcessStateDataAvailable() { - return trackPerProcStateCpuTimes(); + synchronized (this) { + return trackPerProcStateCpuTimes(); + } } @GuardedBy("this") @@ -15862,6 +15891,8 @@ public class BatteryStatsImpl extends BatteryStats { mUptimeUs = in.readLong(); mRealtimeUs = in.readLong(); mStartClockTimeMs = in.readLong(); + mMonotonicStartTime = in.readLong(); + mMonotonicEndTime = in.readLong(); mStartPlatformVersion = in.readString(); mEndPlatformVersion = in.readString(); mOnBatteryTimeBase.readSummaryFromParcel(in); @@ -16382,6 +16413,8 @@ public class BatteryStatsImpl extends BatteryStats { out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED)); out.writeLong(computeRealtime(nowRealtime, STATS_SINCE_CHARGED)); out.writeLong(mStartClockTimeMs); + out.writeLong(mMonotonicStartTime); + out.writeLong(mMonotonicClock.monotonicTime()); out.writeString(mStartPlatformVersion); out.writeString(mEndPlatformVersion); mOnBatteryTimeBase.writeSummaryToParcel(out, nowUptime, nowRealtime); @@ -16912,7 +16945,8 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { + public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart, + BatteryStatsDumpHelper dumpHelper) { if (DEBUG) { pw.println("mOnBatteryTimeBase:"); mOnBatteryTimeBase.dump(pw, " "); @@ -16984,7 +17018,7 @@ public class BatteryStatsImpl extends BatteryStats { pr.println("*** Camera timer:"); mCameraOnTimer.logState(pr, " "); } - super.dump(context, pw, flags, reqUid, histStart); + super.dump(context, pw, flags, reqUid, histStart, dumpHelper); synchronized (this) { pw.print("Per process state tracking available: "); @@ -16998,15 +17032,7 @@ public class BatteryStatsImpl extends BatteryStats { pw.print("UIDs removed since the later of device start or stats reset: "); pw.println(mNumUidsRemoved); - pw.println("Currently mapped isolated uids:"); - final int numIsolatedUids = mIsolatedUids.size(); - for (int i = 0; i < numIsolatedUids; i++) { - final int isolatedUid = mIsolatedUids.keyAt(i); - final int ownerUid = mIsolatedUids.valueAt(i); - final int refCount = mIsolatedUidRefCounts.get(isolatedUid); - pw.println( - " " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")"); - } + mPowerStatsUidResolver.dump(pw); pw.println(); dumpConstantsLocked(pw); @@ -17020,15 +17046,4 @@ public class BatteryStatsImpl extends BatteryStats { dumpEnergyConsumerStatsLocked(pw); } } - - @Override - protected BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed) { - final BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, this); - BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder() - .setMaxStatsAgeMs(0); - if (detailed) { - builder.includePowerModels().includeProcessStateData().includeVirtualUids(); - } - return provider.getBatteryUsageStats(builder.build()); - } } diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 83d7d72059b4..303c2457165a 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -23,14 +23,12 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; -import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; @@ -45,27 +43,25 @@ import java.util.List; public class BatteryUsageStatsProvider { private static final String TAG = "BatteryUsageStatsProv"; private final Context mContext; - private final BatteryStats mStats; + private boolean mPowerStatsExporterEnabled; + private final PowerStatsExporter mPowerStatsExporter; private final PowerStatsStore mPowerStatsStore; private final PowerProfile mPowerProfile; private final CpuScalingPolicies mCpuScalingPolicies; + private final Clock mClock; private final Object mLock = new Object(); private List<PowerCalculator> mPowerCalculators; - public BatteryUsageStatsProvider(Context context, BatteryStats stats) { - this(context, stats, null); - } - - @VisibleForTesting - public BatteryUsageStatsProvider(Context context, BatteryStats stats, - PowerStatsStore powerStatsStore) { + public BatteryUsageStatsProvider(Context context, + PowerStatsExporter powerStatsExporter, + PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies, + PowerStatsStore powerStatsStore, Clock clock) { mContext = context; - mStats = stats; + mPowerStatsExporter = powerStatsExporter; mPowerStatsStore = powerStatsStore; - mPowerProfile = stats instanceof BatteryStatsImpl - ? ((BatteryStatsImpl) stats).getPowerProfile() - : new PowerProfile(context); - mCpuScalingPolicies = stats.getCpuScalingPolicies(); + mPowerProfile = powerProfile; + mCpuScalingPolicies = cpuScalingPolicies; + mClock = clock; } private List<PowerCalculator> getPowerCalculators() { @@ -75,7 +71,10 @@ public class BatteryUsageStatsProvider { // Power calculators are applied in the order of registration mPowerCalculators.add(new BatteryChargeCalculator()); - mPowerCalculators.add(new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); + if (mPowerStatsExporterEnabled) { + mPowerCalculators.add( + new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); + } mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); if (!BatteryStats.checkWifiOnly(mContext)) { @@ -111,27 +110,28 @@ public class BatteryUsageStatsProvider { * Returns true if the last update was too long ago for the tolerances specified * by the supplied queries. */ - public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, - long lastUpdateTimeStampMs) { + public static boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, + long elapsedRealtime, long lastUpdateTimeStampMs) { long allowableStatsAge = Long.MAX_VALUE; for (int i = queries.size() - 1; i >= 0; i--) { BatteryUsageStatsQuery query = queries.get(i); allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge()); } - return elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge; + return elapsedRealtime - lastUpdateTimeStampMs > allowableStatsAge; } /** * Returns snapshots of battery attribution data, one per supplied query. */ - public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { + public List<BatteryUsageStats> getBatteryUsageStats(BatteryStatsImpl stats, + List<BatteryUsageStatsQuery> queries) { ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size()); - synchronized (mStats) { - mStats.prepareForDumpLocked(); - final long currentTimeMillis = currentTimeMillis(); + synchronized (stats) { + stats.prepareForDumpLocked(); + final long currentTimeMillis = mClock.currentTimeMillis(); for (int i = 0; i < queries.size(); i++) { - results.add(getBatteryUsageStats(queries.get(i), currentTimeMillis)); + results.add(getBatteryUsageStats(stats, queries.get(i), currentTimeMillis)); } } return results; @@ -140,60 +140,59 @@ public class BatteryUsageStatsProvider { /** * Returns a snapshot of battery attribution data. */ - @VisibleForTesting - public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { - synchronized (mStats) { - return getBatteryUsageStats(query, currentTimeMillis()); - } + public BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query) { + return getBatteryUsageStats(stats, query, mClock.currentTimeMillis()); } - @GuardedBy("mStats") - private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query, - long currentTimeMs) { + private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query, long currentTimeMs) { if (query.getToTimestamp() == 0) { - return getCurrentBatteryUsageStats(query, currentTimeMs); + return getCurrentBatteryUsageStats(stats, query, currentTimeMs); } else { - return getAggregatedBatteryUsageStats(query); + return getAggregatedBatteryUsageStats(stats, query); } } - @GuardedBy("mStats") - private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query, - long currentTimeMs) { - final long realtimeUs = elapsedRealtime() * 1000; - final long uptimeUs = uptimeMillis() * 1000; + private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query, long currentTimeMs) { + final long realtimeUs = mClock.elapsedRealtime() * 1000; + final long uptimeUs = mClock.uptimeMillis() * 1000; final boolean includePowerModels = (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; final boolean includeProcessStateData = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) - && mStats.isProcessStateDataAvailable(); + && stats.isProcessStateDataAvailable(); final boolean includeVirtualUids = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0); final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder( - mStats.getCustomEnergyConsumerNames(), includePowerModels, + stats.getCustomEnergyConsumerNames(), includePowerModels, includeProcessStateData, minConsumedPowerThreshold); // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration - // of stats sessions to wall-clock adjustments - batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime()); + // of batteryUsageStats sessions to wall-clock adjustments + batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()); batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs); - SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats(); - for (int i = uidStats.size() - 1; i >= 0; i--) { - final BatteryStats.Uid uid = uidStats.valueAt(i); - if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) { - continue; - } + synchronized (stats) { + SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats(); + for (int i = uidStats.size() - 1; i >= 0; i--) { + final BatteryStats.Uid uid = uidStats.valueAt(i); + if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) { + continue; + } - batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) - .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND, - getProcessBackgroundTimeMs(uid, realtimeUs)) - .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND, - getProcessForegroundTimeMs(uid, realtimeUs)) - .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, - getProcessForegroundServiceTimeMs(uid, realtimeUs)); + batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND, + getProcessBackgroundTimeMs(uid, realtimeUs)) + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND, + getProcessForegroundTimeMs(uid, realtimeUs)) + .setTimeInProcessStateMs( + UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, + getProcessForegroundServiceTimeMs(uid, realtimeUs)); + } } final int[] powerComponents = query.getPowerComponents(); @@ -202,8 +201,8 @@ public class BatteryUsageStatsProvider { PowerCalculator powerCalculator = powerCalculators.get(i); if (powerComponents != null) { boolean include = false; - for (int j = 0; j < powerComponents.length; j++) { - if (powerCalculator.isPowerComponentSupported(powerComponents[j])) { + for (int powerComponent : powerComponents) { + if (powerCalculator.isPowerComponentSupported(powerComponent)) { include = true; break; } @@ -212,26 +211,24 @@ public class BatteryUsageStatsProvider { continue; } } - powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs, - query); + powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs, query); + } + + if (mPowerStatsExporterEnabled) { + mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder, + stats.getMonotonicStartTime(), stats.getMonotonicEndTime()); } if ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) { - if (!(mStats instanceof BatteryStatsImpl)) { - throw new UnsupportedOperationException( - "History cannot be included for " + getClass().getName()); - } - - BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats; - batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory()); + batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory()); } - BatteryUsageStats stats = batteryUsageStatsBuilder.build(); + BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build(); if (includeProcessStateData) { - verify(stats); + verify(batteryUsageStats); } - return stats; + return batteryUsageStats; } // STOPSHIP(b/229906525): remove verification before shipping @@ -308,15 +305,16 @@ public class BatteryUsageStatsProvider { / 1000; } - private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) { + private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query) { final boolean includePowerModels = (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; final boolean includeProcessStateData = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) - && mStats.isProcessStateDataAvailable(); + && stats.isProcessStateDataAvailable(); final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); - final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames(); + final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames(); final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( customEnergyConsumerNames, includePowerModels, includeProcessStateData, minConsumedPowerThreshold); @@ -386,27 +384,8 @@ public class BatteryUsageStatsProvider { return builder.build(); } - private long elapsedRealtime() { - if (mStats instanceof BatteryStatsImpl) { - return ((BatteryStatsImpl) mStats).mClock.elapsedRealtime(); - } else { - return SystemClock.elapsedRealtime(); - } - } + public void setPowerStatsExporterEnabled(boolean enabled) { - private long uptimeMillis() { - if (mStats instanceof BatteryStatsImpl) { - return ((BatteryStatsImpl) mStats).mClock.uptimeMillis(); - } else { - return SystemClock.uptimeMillis(); - } - } - - private long currentTimeMillis() { - if (mStats instanceof BatteryStatsImpl) { - return ((BatteryStatsImpl) mStats).mClock.currentTimeMillis(); - } else { - return System.currentTimeMillis(); - } + mPowerStatsExporterEnabled = enabled; } } diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java index f40eef2ed820..ed9414ff53a1 100644 --- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java @@ -64,7 +64,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces private PowerStats.Descriptor mLastUsedDescriptor; // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when // mLastUsedDescriptor changes - private CpuPowerStatsCollector.StatsArrayLayout mStatsLayout; + private CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout; // Sequence of steps for power estimation and intermediate results. private PowerEstimationPlan mPlan; @@ -106,7 +106,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces } mLastUsedDescriptor = descriptor; - mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout(); + mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); mStatsLayout.fromExtras(descriptor.extras); mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; @@ -149,7 +149,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces if (mPlan == null) { mPlan = new PowerEstimationPlan(stats.getConfig()); - if (mStatsLayout.getCpuClusterEnergyConsumerCount() != 0) { + if (mStatsLayout.getEnergyConsumerCount() != 0) { initEnergyConsumerToPowerBracketMaps(); } } @@ -212,7 +212,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces * CL_2: [bracket3, bracket4] */ private void initEnergyConsumerToPowerBracketMaps() { - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount]; @@ -294,7 +294,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces continue; } - intermediates.uptime += mStatsLayout.getUptime(mTmpDeviceStatsArray); + intermediates.uptime += mStatsLayout.getUsageDuration(mTmpDeviceStatsArray); for (int cluster = 0; cluster < mCpuClusterCount; cluster++) { intermediates.timeByCluster[cluster] += @@ -351,7 +351,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap(); - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations; for (int dse = deviceStateEstimations.size() - 1; dse >= 0; dse--) { DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(dse); @@ -392,7 +392,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces private void adjustEstimatesUsingEnergyConsumers( Intermediates intermediates, DeviceStatsIntermediates deviceStatsIntermediates) { - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); if (energyConsumerCount == 0) { return; } @@ -509,8 +509,8 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces } sb.append(mStatsLayout.getTimeByCluster(stats, cluster)); } - sb.append("] uptime: ").append(mStatsLayout.getUptime(stats)); - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats)); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); if (energyConsumerCount > 0) { sb.append(" energy: ["); for (int i = 0; i < energyConsumerCount; i++) { diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java index b8e581f32a58..c05407cb6d17 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -22,6 +22,7 @@ import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.Handler; import android.os.PersistableBundle; +import android.os.Process; import android.power.PowerStatsInternal; import android.util.Slog; import android.util.SparseArray; @@ -67,6 +68,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private final CpuScalingPolicies mCpuScalingPolicies; private final PowerProfile mPowerProfile; private final KernelCpuStatsReader mKernelCpuStatsReader; + private final PowerStatsUidResolver mUidResolver; private final Supplier<PowerStatsInternal> mPowerStatsSupplier; private final IntSupplier mVoltageSupplier; private final int mDefaultCpuPowerBrackets; @@ -81,7 +83,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private PowerStats.Descriptor mPowerStatsDescriptor; // Reusable instance private PowerStats mCpuPowerStats; - private StatsArrayLayout mLayout; + private CpuStatsArrayLayout mLayout; private long mLastUpdateTimestampNanos; private long mLastUpdateUptimeMillis; private int mLastVoltageMv; @@ -91,55 +93,30 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Captures the positions and lengths of sections of the stats array, such as time-in-state, * power usage estimates etc. */ - public static class StatsArrayLayout { + public static class CpuStatsArrayLayout extends StatsArrayLayout { private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt"; private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc"; private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc"; private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc"; - private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; - private static final String EXTRA_DEVICE_UPTIME_POSITION = "du"; - private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; - private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; private static final String EXTRA_UID_BRACKETS_POSITION = "ub"; private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us"; - private static final String EXTRA_UID_POWER_POSITION = "up"; - - private static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; - - private int mDeviceStatsArrayLength; - private int mUidStatsArrayLength; private int mDeviceCpuTimeByScalingStepPosition; private int mDeviceCpuTimeByScalingStepCount; private int mDeviceCpuTimeByClusterPosition; private int mDeviceCpuTimeByClusterCount; - private int mDeviceCpuUptimePosition; - private int mDeviceEnergyConsumerPosition; - private int mDeviceEnergyConsumerCount; - private int mDevicePowerEstimatePosition; private int mUidPowerBracketsPosition; private int mUidPowerBracketCount; - private int[][] mEnergyConsumerToPowerBucketMaps; - private int mUidPowerEstimatePosition; private int[] mScalingStepToPowerBracketMap; - public int getDeviceStatsArrayLength() { - return mDeviceStatsArrayLength; - } - - public int getUidStatsArrayLength() { - return mUidStatsArrayLength; - } - /** * Declare that the stats array has a section capturing CPU time per scaling step */ public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { - mDeviceCpuTimeByScalingStepPosition = mDeviceStatsArrayLength; + mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount); mDeviceCpuTimeByScalingStepCount = scalingStepCount; - mDeviceStatsArrayLength += scalingStepCount; } public int getCpuScalingStepCount() { @@ -166,9 +143,8 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Declare that the stats array has a section capturing CPU time in each cluster */ public void addDeviceSectionCpuTimeByCluster(int clusterCount) { + mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount); mDeviceCpuTimeByClusterCount = clusterCount; - mDeviceCpuTimeByClusterPosition = mDeviceStatsArrayLength; - mDeviceStatsArrayLength += clusterCount; } public int getCpuClusterCount() { @@ -192,86 +168,12 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { } /** - * Declare that the stats array has a section capturing CPU uptime - */ - public void addDeviceSectionUptime() { - mDeviceCpuUptimePosition = mDeviceStatsArrayLength++; - } - - /** - * Saves the CPU uptime duration in the corresponding <code>stats</code> element. - */ - public void setUptime(long[] stats, long value) { - stats[mDeviceCpuUptimePosition] = value; - } - - /** - * Extracts the CPU uptime duration from the corresponding <code>stats</code> element. - */ - public long getUptime(long[] stats) { - return stats[mDeviceCpuUptimePosition]; - } - - /** - * Declares that the stats array has a section capturing EnergyConsumer data from - * PowerStatsService. - */ - public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { - mDeviceEnergyConsumerPosition = mDeviceStatsArrayLength; - mDeviceEnergyConsumerCount = energyConsumerCount; - mDeviceStatsArrayLength += energyConsumerCount; - } - - public int getCpuClusterEnergyConsumerCount() { - return mDeviceEnergyConsumerCount; - } - - /** - * Saves the accumulated energy for the specified rail the corresponding - * <code>stats</code> element. - */ - public void setConsumedEnergy(long[] stats, int index, long energy) { - stats[mDeviceEnergyConsumerPosition + index] = energy; - } - - /** - * Extracts the EnergyConsumer data from a device stats array for the specified - * EnergyConsumer. - */ - public long getConsumedEnergy(long[] stats, int index) { - return stats[mDeviceEnergyConsumerPosition + index]; - } - - /** - * Declare that the stats array has a section capturing a power estimate - */ - public void addDeviceSectionPowerEstimate() { - mDevicePowerEstimatePosition = mDeviceStatsArrayLength++; - } - - /** - * Converts the supplied mAh power estimate to a long and saves it in the corresponding - * element of <code>stats</code>. - */ - public void setDevicePowerEstimate(long[] stats, double power) { - stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); - } - - /** - * Extracts the power estimate from a device stats array and converts it to mAh. - */ - public double getDevicePowerEstimate(long[] stats) { - return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; - } - - /** * Declare that the UID stats array has a section capturing CPU time per power bracket. */ public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; - mUidPowerBracketsPosition = mUidStatsArrayLength; updatePowerBracketCount(); - mUidStatsArrayLength += mUidPowerBracketCount; + mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount); } private void updatePowerBracketCount() { @@ -306,31 +208,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { } /** - * Declare that the UID stats array has a section capturing a power estimate - */ - public void addUidSectionPowerEstimate() { - mUidPowerEstimatePosition = mUidStatsArrayLength++; - } - - /** - * Converts the supplied mAh power estimate to a long and saves it in the corresponding - * element of <code>stats</code>. - */ - public void setUidPowerEstimate(long[] stats, double power) { - stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); - } - - /** - * Extracts the power estimate from a UID stats array and converts it to mAh. - */ - public double getUidPowerEstimate(long[] stats) { - return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; - } - - /** * Copies the elements of the stats array layout into <code>extras</code> */ public void toExtras(PersistableBundle extras) { + super.toExtras(extras); extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION, mDeviceCpuTimeByScalingStepPosition); extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT, @@ -339,22 +220,16 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mDeviceCpuTimeByClusterPosition); extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT, mDeviceCpuTimeByClusterCount); - extras.putInt(EXTRA_DEVICE_UPTIME_POSITION, mDeviceCpuUptimePosition); - extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, - mDeviceEnergyConsumerPosition); - extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, - mDeviceEnergyConsumerCount); - extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition); - extras.putIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, + putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, mScalingStepToPowerBracketMap); - extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); } /** * Retrieves elements of the stats array layout from <code>extras</code> */ public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); mDeviceCpuTimeByScalingStepPosition = extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION); mDeviceCpuTimeByScalingStepCount = @@ -363,43 +238,39 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION); mDeviceCpuTimeByClusterCount = extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT); - mDeviceCpuUptimePosition = extras.getInt(EXTRA_DEVICE_UPTIME_POSITION); - mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); - mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); - mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION); mScalingStepToPowerBracketMap = - extras.getIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); + getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); if (mScalingStepToPowerBracketMap == null) { mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount]; } updatePowerBracketCount(); - mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); } } public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - IntSupplier voltageSupplier, Handler handler, long throttlePeriodMs) { - this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), + PowerStatsUidResolver uidResolver, IntSupplier voltageSupplier, Handler handler, + long throttlePeriodMs) { + this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), uidResolver, () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier, throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS, DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER); } public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - Handler handler, KernelCpuStatsReader kernelCpuStatsReader, - Supplier<PowerStatsInternal> powerStatsSupplier, + Handler handler, KernelCpuStatsReader kernelCpuStatsReader, + PowerStatsUidResolver uidResolver, Supplier<PowerStatsInternal> powerStatsSupplier, IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock, int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { super(handler, throttlePeriodMs, clock); mCpuScalingPolicies = cpuScalingPolicies; mPowerProfile = powerProfile; mKernelCpuStatsReader = kernelCpuStatsReader; + mUidResolver = uidResolver; mPowerStatsSupplier = powerStatsSupplier; mVoltageSupplier = voltageSupplier; mDefaultCpuPowerBrackets = defaultCpuPowerBrackets; mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer; - } /** @@ -409,13 +280,13 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { setEnabled(Flags.streamlinedBatteryStats()); } - private void ensureInitialized() { + private boolean ensureInitialized() { if (mIsInitialized) { - return; + return true; } if (!isEnabled()) { - return; + return false; } mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature(); @@ -432,10 +303,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mTempCpuTimeByScalingStep = new long[cpuScalingStepCount]; int[] scalingStepToPowerBracketMap = initPowerBrackets(); - mLayout = new StatsArrayLayout(); + mLayout = new CpuStatsArrayLayout(); mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount); mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length); - mLayout.addDeviceSectionUptime(); + mLayout.addDeviceSectionUsageDuration(); mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length); mLayout.addDeviceSectionPowerEstimate(); mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap); @@ -451,6 +322,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mTempUidStats = new long[mLayout.getCpuPowerBracketCount()]; mIsInitialized = true; + return true; } private void readCpuEnergyConsumerIds() { @@ -590,7 +462,9 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Prints the definitions of power brackets. */ public void dumpCpuPowerBracketsLocked(PrintWriter pw) { - ensureInitialized(); + if (!ensureInitialized()) { + return; + } if (mLayout == null) { return; @@ -610,7 +484,9 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { */ @VisibleForTesting public String getCpuPowerBracketDescription(int powerBracket) { - ensureInitialized(); + if (!ensureInitialized()) { + return ""; + } int[] stepToPowerBracketMap = mLayout.getScalingStepToPowerBracketMap(); StringBuilder sb = new StringBuilder(); @@ -647,14 +523,18 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { */ @VisibleForTesting public PowerStats.Descriptor getPowerStatsDescriptor() { - ensureInitialized(); + if (!ensureInitialized()) { + return null; + } return mPowerStatsDescriptor; } @Override protected PowerStats collectStats() { - ensureInitialized(); + if (!ensureInitialized()) { + return null; + } if (!mIsPerUidTimeInStateSupported) { return null; @@ -682,7 +562,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { if (uptimeDelta > mCpuPowerStats.durationMs) { uptimeDelta = mCpuPowerStats.durationMs; } - mLayout.setUptime(mCpuPowerStats.stats, uptimeDelta); + mLayout.setUsageDuration(mCpuPowerStats.stats, uptimeDelta); if (mCpuEnergyConsumerIds.length != 0) { collectEnergyConsumers(); @@ -761,7 +641,21 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { uidStats.timeByPowerBracket[bracket] = timeByPowerBracket[bracket]; } if (nonzero) { - mCpuPowerStats.uidStats.put(uid, uidStats.stats); + int ownerUid; + if (Process.isSdkSandboxUid(uid)) { + ownerUid = Process.getAppUidForSdkSandboxUid(uid); + } else { + ownerUid = mUidResolver.mapUid(uid); + } + + long[] ownerStats = mCpuPowerStats.uidStats.get(ownerUid); + if (ownerStats == null) { + mCpuPowerStats.uidStats.put(ownerUid, uidStats.stats); + } else { + for (int i = 0; i < ownerStats.length; i++) { + ownerStats[i] += uidStats.stats[i]; + } + } } } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 2c7843e626c9..0facb9c01d74 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -44,6 +44,7 @@ class PowerComponentAggregatedPowerStats { private static final String XML_TAG_DEVICE_STATS = "device-stats"; private static final String XML_TAG_UID_STATS = "uid-stats"; private static final String XML_ATTR_UID = "uid"; + private static final long UNKNOWN = -1; public final int powerComponentId; private final MultiStateStats.States[] mDeviceStateConfig; @@ -51,17 +52,16 @@ class PowerComponentAggregatedPowerStats { @NonNull private final AggregatedPowerStatsConfig.PowerComponent mConfig; private final int[] mDeviceStates; - private final long[] mDeviceStateTimestamps; private MultiStateStats.Factory mStatsFactory; private MultiStateStats.Factory mUidStatsFactory; private PowerStats.Descriptor mPowerStatsDescriptor; + private long mPowerStatsTimestamp; private MultiStateStats mDeviceStats; private final SparseArray<UidStats> mUidStats = new SparseArray<>(); private static class UidStats { public int[] states; - public long[] stateTimestampMs; public MultiStateStats stats; } @@ -71,7 +71,7 @@ class PowerComponentAggregatedPowerStats { mDeviceStateConfig = config.getDeviceStateConfig(); mUidStateConfig = config.getUidStateConfig(); mDeviceStates = new int[mDeviceStateConfig.length]; - mDeviceStateTimestamps = new long[mDeviceStateConfig.length]; + mPowerStatsTimestamp = UNKNOWN; } @NonNull @@ -85,8 +85,11 @@ class PowerComponentAggregatedPowerStats { } void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { + if (mDeviceStats == null) { + createDeviceStats(); + } + mDeviceStates[stateId] = state; - mDeviceStateTimestamps[stateId] = time; if (mDeviceStateConfig[stateId].isTracked()) { if (mDeviceStats != null) { @@ -97,6 +100,11 @@ class PowerComponentAggregatedPowerStats { if (mUidStateConfig[stateId].isTracked()) { for (int i = mUidStats.size() - 1; i >= 0; i--) { PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); + if (uidStats.stats == null) { + createUidStats(uidStats); + } + + uidStats.states[stateId] = state; if (uidStats.stats != null) { uidStats.stats.setState(stateId, state, time); } @@ -111,8 +119,11 @@ class PowerComponentAggregatedPowerStats { } UidStats uidStats = getUidStats(uid); + if (uidStats.stats == null) { + createUidStats(uidStats); + } + uidStats.states[stateId] = state; - uidStats.stateTimestampMs[stateId] = time; if (uidStats.stats != null) { uidStats.stats.setState(stateId, state, time); @@ -150,10 +161,11 @@ class PowerComponentAggregatedPowerStats { } uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs); } + + mPowerStatsTimestamp = timestampMs; } void reset() { - mPowerStatsDescriptor = null; mStatsFactory = null; mUidStatsFactory = null; mDeviceStats = null; @@ -163,12 +175,10 @@ class PowerComponentAggregatedPowerStats { } private UidStats getUidStats(int uid) { - // TODO(b/292247660): map isolated and sandbox UIDs UidStats uidStats = mUidStats.get(uid); if (uidStats == null) { uidStats = new UidStats(); uidStats.states = new int[mUidStateConfig.length]; - uidStats.stateTimestampMs = new long[mUidStateConfig.length]; mUidStats.put(uid, uidStats); } return uidStats; @@ -209,42 +219,38 @@ class PowerComponentAggregatedPowerStats { return false; } - private boolean createDeviceStats() { + private void createDeviceStats() { if (mStatsFactory == null) { if (mPowerStatsDescriptor == null) { - return false; + return; } mStatsFactory = new MultiStateStats.Factory( mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig); } mDeviceStats = mStatsFactory.create(); - for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { - mDeviceStats.setState(stateId, mDeviceStates[stateId], - mDeviceStateTimestamps[stateId]); + if (mPowerStatsTimestamp != UNKNOWN) { + for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { + mDeviceStats.setState(stateId, mDeviceStates[stateId], mPowerStatsTimestamp); + } } - return true; } - private boolean createUidStats(UidStats uidStats) { + private void createUidStats(UidStats uidStats) { if (mUidStatsFactory == null) { if (mPowerStatsDescriptor == null) { - return false; + return; } mUidStatsFactory = new MultiStateStats.Factory( mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig); } uidStats.stats = mUidStatsFactory.create(); - for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { - uidStats.stats.setState(stateId, mDeviceStates[stateId], - mDeviceStateTimestamps[stateId]); - } - for (int stateId = mDeviceStateConfig.length; stateId < mUidStateConfig.length; stateId++) { - uidStats.stats.setState(stateId, uidStats.states[stateId], - uidStats.stateTimestampMs[stateId]); + for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) { + if (mPowerStatsTimestamp != UNKNOWN) { + uidStats.stats.setState(stateId, uidStats.states[stateId], mPowerStatsTimestamp); + } } - return true; } public void writeXml(TypedXmlSerializer serializer) throws IOException { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java index 2f9d5674d78a..3f88a2d2dec0 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -29,13 +29,18 @@ import java.util.function.Consumer; * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history. */ public class PowerStatsAggregator { + private static final long UNINITIALIZED = -1; private final AggregatedPowerStats mStats; + private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; private final BatteryStatsHistory mHistory; private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>(); + private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; + private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, BatteryStatsHistory history) { mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig); + mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig; mHistory = history; for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig : aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) { @@ -44,6 +49,10 @@ public class PowerStatsAggregator { } } + AggregatedPowerStatsConfig getConfig() { + return mAggregatedPowerStatsConfig; + } + /** * Iterates of the battery history and aggregates power stats between the specified times. * The start and end are specified in the battery-stats monotonic time, which is the @@ -58,18 +67,20 @@ public class PowerStatsAggregator { */ public void aggregatePowerStats(long startTimeMs, long endTimeMs, Consumer<AggregatedPowerStats> consumer) { - int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; - int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; - long baseTime = -1; + boolean clockUpdateAdded = false; + long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED; long lastTime = 0; try (BatteryStatsHistoryIterator iterator = mHistory.copy().iterate(startTimeMs, endTimeMs)) { while (iterator.hasNext()) { BatteryStats.HistoryItem item = iterator.next(); - if (baseTime < 0) { + if (!clockUpdateAdded) { mStats.addClockUpdate(item.time, item.currentTime); - baseTime = item.time; + if (baseTime == UNINITIALIZED) { + baseTime = item.time; + } + clockUpdateAdded = true; } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME || item.cmd == BatteryStats.HistoryItem.CMD_RESET) { mStats.addClockUpdate(item.time, item.currentTime); @@ -81,20 +92,20 @@ public class PowerStatsAggregator { (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0 ? AggregatedPowerStatsConfig.POWER_STATE_OTHER : AggregatedPowerStatsConfig.POWER_STATE_BATTERY; - if (batteryState != currentBatteryState) { + if (batteryState != mCurrentBatteryState) { mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState, item.time); - currentBatteryState = batteryState; + mCurrentBatteryState = batteryState; } int screenState = (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0 ? AggregatedPowerStatsConfig.SCREEN_STATE_ON : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; - if (screenState != currentScreenState) { + if (screenState != mCurrentScreenState) { mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState, item.time); - currentScreenState = screenState; + mCurrentScreenState = screenState; } if (item.processStateChange != null) { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index 84cc21e81536..abfe9debc7de 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -16,10 +16,13 @@ package com.android.server.power.stats; +import android.annotation.Nullable; import android.os.ConditionVariable; import android.os.Handler; +import android.os.PersistableBundle; import android.util.FastImmutableArraySet; import android.util.IndentingPrintWriter; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.Clock; @@ -38,6 +41,7 @@ import java.util.stream.Stream; * except where noted. */ public abstract class PowerStatsCollector { + private static final String TAG = "PowerStatsCollector"; private static final int MILLIVOLTS_PER_VOLT = 1000; private final Handler mHandler; protected final Clock mClock; @@ -46,6 +50,200 @@ public abstract class PowerStatsCollector { private boolean mEnabled; private long mLastScheduledUpdateMs = -1; + /** + * Captures the positions and lengths of sections of the stats array, such as usage duration, + * power usage estimates etc. + */ + public static class StatsArrayLayout { + private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; + private static final String EXTRA_DEVICE_DURATION_POSITION = "dd"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; + private static final String EXTRA_UID_POWER_POSITION = "up"; + + protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; + + private int mDeviceStatsArrayLength; + private int mUidStatsArrayLength; + + protected int mDeviceDurationPosition; + private int mDeviceEnergyConsumerPosition; + private int mDeviceEnergyConsumerCount; + private int mDevicePowerEstimatePosition; + private int mUidPowerEstimatePosition; + + public int getDeviceStatsArrayLength() { + return mDeviceStatsArrayLength; + } + + public int getUidStatsArrayLength() { + return mUidStatsArrayLength; + } + + protected int addDeviceSection(int length) { + int position = mDeviceStatsArrayLength; + mDeviceStatsArrayLength += length; + return position; + } + + protected int addUidSection(int length) { + int position = mUidStatsArrayLength; + mUidStatsArrayLength += length; + return position; + } + + /** + * Declare that the stats array has a section capturing usage duration + */ + public void addDeviceSectionUsageDuration() { + mDeviceDurationPosition = addDeviceSection(1); + } + + /** + * Saves the usage duration in the corresponding <code>stats</code> element. + */ + public void setUsageDuration(long[] stats, long value) { + stats[mDeviceDurationPosition] = value; + } + + /** + * Extracts the usage duration from the corresponding <code>stats</code> element. + */ + public long getUsageDuration(long[] stats) { + return stats[mDeviceDurationPosition]; + } + + /** + * Declares that the stats array has a section capturing EnergyConsumer data from + * PowerStatsService. + */ + public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { + mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount); + mDeviceEnergyConsumerCount = energyConsumerCount; + } + + public int getEnergyConsumerCount() { + return mDeviceEnergyConsumerCount; + } + + /** + * Saves the accumulated energy for the specified rail the corresponding + * <code>stats</code> element. + */ + public void setConsumedEnergy(long[] stats, int index, long energy) { + stats[mDeviceEnergyConsumerPosition + index] = energy; + } + + /** + * Extracts the EnergyConsumer data from a device stats array for the specified + * EnergyConsumer. + */ + public long getConsumedEnergy(long[] stats, int index) { + return stats[mDeviceEnergyConsumerPosition + index]; + } + + /** + * Declare that the stats array has a section capturing a power estimate + */ + public void addDeviceSectionPowerEstimate() { + mDevicePowerEstimatePosition = addDeviceSection(1); + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setDevicePowerEstimate(long[] stats, double power) { + stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a device stats array and converts it to mAh. + */ + public double getDevicePowerEstimate(long[] stats) { + return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Declare that the UID stats array has a section capturing a power estimate + */ + public void addUidSectionPowerEstimate() { + mUidPowerEstimatePosition = addUidSection(1); + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setUidPowerEstimate(long[] stats, double power) { + stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a UID stats array and converts it to mAh. + */ + public double getUidPowerEstimate(long[] stats) { + return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, + mDeviceEnergyConsumerPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, + mDeviceEnergyConsumerCount); + extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); + extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION); + mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); + mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); + mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); + mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); + } + + protected void putIntArray(PersistableBundle extras, String key, int[] array) { + if (array == null) { + return; + } + + StringBuilder sb = new StringBuilder(); + for (int value : array) { + if (!sb.isEmpty()) { + sb.append(','); + } + sb.append(value); + } + extras.putString(key, sb.toString()); + } + + protected int[] getIntArray(PersistableBundle extras, String key) { + String string = extras.getString(key); + if (string == null) { + return null; + } + String[] values = string.trim().split(","); + int[] result = new int[values.length]; + for (int i = 0; i < values.length; i++) { + try { + result[i] = Integer.parseInt(values[i]); + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Invalid CSV format: " + string); + return null; + } + } + return result; + } + } + @GuardedBy("this") @SuppressWarnings("unchecked") private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList = @@ -141,6 +339,7 @@ public abstract class PowerStatsCollector { return true; } + @Nullable protected abstract PowerStats collectStats(); /** diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java new file mode 100644 index 000000000000..70c24d58bb2a --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -0,0 +1,227 @@ +/* + * 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.power.stats; + +import android.os.AggregateBatteryConsumer; +import android.os.BatteryConsumer; +import android.os.BatteryUsageStats; +import android.os.UidBatteryConsumer; +import android.util.Slog; + +import com.android.internal.os.MultiStateStats; +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Given a time range, converts accumulated PowerStats to BatteryUsageStats. Combines + * stores spans of PowerStats with the yet-unprocessed tail of battery history. + */ +public class PowerStatsExporter { + private static final String TAG = "PowerStatsExporter"; + private final PowerStatsStore mPowerStatsStore; + private final PowerStatsAggregator mPowerStatsAggregator; + private final long mBatterySessionTimeSpanSlackMillis; + private static final long BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS = TimeUnit.MINUTES.toMillis(2); + + public PowerStatsExporter(PowerStatsStore powerStatsStore, + PowerStatsAggregator powerStatsAggregator) { + this(powerStatsStore, powerStatsAggregator, BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS); + } + + public PowerStatsExporter(PowerStatsStore powerStatsStore, + PowerStatsAggregator powerStatsAggregator, + long batterySessionTimeSpanSlackMillis) { + mPowerStatsStore = powerStatsStore; + mPowerStatsAggregator = powerStatsAggregator; + mBatterySessionTimeSpanSlackMillis = batterySessionTimeSpanSlackMillis; + } + + /** + * Populates the provided BatteryUsageStats.Builder with power estimates from the accumulated + * PowerStats, both stored in PowerStatsStore and not-yet processed. + */ + public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder, + long monotonicStartTime, long monotonicEndTime) { + long maxEndTime = monotonicStartTime; + List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents(); + for (int i = spans.size() - 1; i >= 0; i--) { + PowerStatsSpan.Metadata metadata = spans.get(i); + if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) { + continue; + } + + List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames(); + long spanMinTime = Long.MAX_VALUE; + long spanMaxTime = Long.MIN_VALUE; + for (int j = 0; j < timeFrames.size(); j++) { + PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j); + long startMonotonicTime = timeFrame.startMonotonicTime; + long endMonotonicTime = startMonotonicTime + timeFrame.duration; + if (startMonotonicTime < spanMinTime) { + spanMinTime = startMonotonicTime; + } + if (endMonotonicTime > spanMaxTime) { + spanMaxTime = endMonotonicTime; + } + } + + if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) { + continue; + } + + if (spanMaxTime > maxEndTime) { + maxEndTime = spanMaxTime; + } + + PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), + AggregatedPowerStatsSection.TYPE); + if (span == null) { + Slog.e(TAG, "Could not read PowerStatsStore section " + metadata); + continue; + } + List<PowerStatsSpan.Section> sections = span.getSections(); + for (int k = 0; k < sections.size(); k++) { + PowerStatsSpan.Section section = sections.get(k); + populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, + ((AggregatedPowerStatsSection) section).getAggregatedPowerStats()); + } + } + + if (maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) { + mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime, + stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats)); + } + } + + private void populateBatteryUsageStatsBuilder( + BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats) { + AggregatedPowerStatsConfig config = mPowerStatsAggregator.getConfig(); + List<AggregatedPowerStatsConfig.PowerComponent> powerComponents = + config.getPowerComponentsAggregatedStatsConfigs(); + for (int i = powerComponents.size() - 1; i >= 0; i--) { + populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats, + powerComponents.get(i)); + } + } + + private void populateBatteryUsageStatsBuilder( + BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats, + AggregatedPowerStatsConfig.PowerComponent powerComponent) { + int powerComponentId = powerComponent.getPowerComponentId(); + PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats( + powerComponentId); + if (powerComponentStats == null) { + return; + } + + PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor(); + if (descriptor == null) { + return; + } + + PowerStatsCollector.StatsArrayLayout layout = new PowerStatsCollector.StatsArrayLayout(); + layout.fromExtras(descriptor.extras); + + long[] deviceStats = new long[descriptor.statsArrayLength]; + double[] totalPower = new double[1]; + MultiStateStats.States.forEachTrackedStateCombination(powerComponent.getDeviceStateConfig(), + states -> { + if (states[AggregatedPowerStatsConfig.STATE_POWER] + != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + return; + } + + if (!powerComponentStats.getDeviceStats(deviceStats, states)) { + return; + } + + totalPower[0] += layout.getDevicePowerEstimate(deviceStats); + }); + + AggregateBatteryConsumer.Builder deviceScope = + batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); + deviceScope.addConsumedPower(powerComponentId, + totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); + + long[] uidStats = new long[descriptor.uidStatsArrayLength]; + ArrayList<Integer> uids = new ArrayList<>(); + powerComponentStats.collectUids(uids); + + boolean breakDownByProcState = + batteryUsageStatsBuilder.isProcessStateDataNeeded() + && powerComponent + .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE] + .isTracked(); + + double[] powerByProcState = + new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1]; + double powerAllApps = 0; + for (int uid : uids) { + UidBatteryConsumer.Builder builder = + batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid); + + Arrays.fill(powerByProcState, 0); + + MultiStateStats.States.forEachTrackedStateCombination( + powerComponent.getUidStateConfig(), + states -> { + if (states[AggregatedPowerStatsConfig.STATE_POWER] + != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + return; + } + + if (!powerComponentStats.getUidStats(uidStats, uid, states)) { + return; + } + + double power = layout.getUidPowerEstimate(uidStats); + int procState = breakDownByProcState + ? states[AggregatedPowerStatsConfig.STATE_PROCESS_STATE] + : BatteryConsumer.PROCESS_STATE_UNSPECIFIED; + powerByProcState[procState] += power; + }); + + double powerAllProcStates = 0; + for (int procState = 0; procState < powerByProcState.length; procState++) { + double power = powerByProcState[procState]; + if (power == 0) { + continue; + } + powerAllProcStates += power; + if (breakDownByProcState + && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + builder.addConsumedPower(builder.getKey(powerComponentId, procState), + power, BatteryConsumer.POWER_MODEL_UNDEFINED); + } + } + builder.addConsumedPower(powerComponentId, powerAllProcStates, + BatteryConsumer.POWER_MODEL_UNDEFINED); + powerAllApps += powerAllProcStates; + } + + AggregateBatteryConsumer.Builder allAppsScope = + batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + allAppsScope.addConsumedPower(powerComponentId, powerAllApps, + BatteryConsumer.POWER_MODEL_UNDEFINED); + } +} diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java index 551302ee8f14..97d872a1a539 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -19,8 +19,6 @@ package com.android.server.power.stats; import android.annotation.DurationMillisLong; import android.app.AlarmManager; import android.content.Context; -import android.os.BatteryUsageStats; -import android.os.BatteryUsageStatsQuery; import android.os.ConditionVariable; import android.os.Handler; import android.util.IndentingPrintWriter; @@ -52,7 +50,6 @@ public class PowerStatsScheduler { private final MonotonicClock mMonotonicClock; private final Handler mHandler; private final BatteryStatsImpl mBatteryStats; - private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; private final PowerStatsAggregator mPowerStatsAggregator; private long mLastSavedSpanEndMonotonicTime; @@ -60,7 +57,7 @@ public class PowerStatsScheduler { @DurationMillisLong long aggregatedPowerStatsSpanDuration, @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, Clock clock, MonotonicClock monotonicClock, Handler handler, - BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) { + BatteryStatsImpl batteryStats) { mContext = context; mPowerStatsAggregator = powerStatsAggregator; mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration; @@ -70,16 +67,15 @@ public class PowerStatsScheduler { mMonotonicClock = monotonicClock; mHandler = handler; mBatteryStats = batteryStats; - mBatteryUsageStatsProvider = batteryUsageStatsProvider; } /** * Kicks off the scheduling of power stats aggregation spans. */ public void start(boolean enablePeriodicPowerStatsCollection) { - mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset); mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection; if (mEnablePeriodicPowerStatsCollection) { + schedulePowerStatsAggregation(); scheduleNextPowerStatsAggregation(); } } @@ -235,28 +231,6 @@ public class PowerStatsScheduler { mPowerStatsStore.storeAggregatedPowerStats(stats); } - private void storeBatteryUsageStatsOnReset(int resetReason) { - if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) { - return; - } - - final BatteryUsageStats batteryUsageStats = - mBatteryUsageStatsProvider.getBatteryUsageStats( - new BatteryUsageStatsQuery.Builder() - .setMaxStatsAgeMs(0) - .includePowerModels() - .includeProcessStateData() - .build()); - - // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end - // Once that change is made, we will be able to use the BatteryUsageStats' monotonic - // start time - long monotonicStartTime = - mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration(); - mHandler.post(() -> - mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats)); - } - private void awaitCompletion() { ConditionVariable done = new ConditionVariable(); mHandler.post(done::open); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java b/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java new file mode 100644 index 000000000000..8dc360983239 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java @@ -0,0 +1,241 @@ +/* + * 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.power.stats; + +import android.util.IntArray; +import android.util.Slog; +import android.util.SparseIntArray; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Maintains a map of isolated UIDs to their respective owner UIDs, to support combining + * power stats for isolated UIDs, which are typically short-lived, into the corresponding app UID. + */ +public class PowerStatsUidResolver { + private static final String TAG = "PowerStatsUidResolver"; + + /** + * Listener notified when isolated UIDs are created and removed. + */ + public interface Listener { + + /** + * Callback invoked when a new isolated UID is registered. + */ + void onIsolatedUidAdded(int isolatedUid, int parentUid); + + /** + * Callback invoked before an isolated UID is evicted from the resolver. + * If the listener calls {@link PowerStatsUidResolver#retainIsolatedUid}, the mapping + * will be retained until {@link PowerStatsUidResolver#releaseIsolatedUid} is called. + */ + void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid); + + /** + * Callback invoked when an isolated UID to owner UID mapping is removed. + */ + void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid); + } + + /** + * Mapping isolated uids to the actual owning app uid. + */ + private final SparseIntArray mIsolatedUids = new SparseIntArray(); + + /** + * Internal reference count of isolated uids. + */ + private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray(); + + // Keep the list read-only in order to avoid locking during the delivery of listener calls. + private volatile List<Listener> mListeners = Collections.emptyList(); + + /** + * Adds a listener. + */ + public void addListener(Listener listener) { + synchronized (this) { + List<Listener> newList = new ArrayList<>(mListeners); + newList.add(listener); + mListeners = Collections.unmodifiableList(newList); + } + } + + /** + * Removes a listener. + */ + public void removeListener(Listener listener) { + synchronized (this) { + List<Listener> newList = new ArrayList<>(mListeners); + newList.remove(listener); + mListeners = Collections.unmodifiableList(newList); + } + } + + /** + * Remembers the connection between a newly created isolated UID and its owner app UID. + * Calls {@link Listener#onIsolatedUidAdded} on each registered listener. + */ + public void noteIsolatedUidAdded(int isolatedUid, int parentUid) { + synchronized (this) { + mIsolatedUids.put(isolatedUid, parentUid); + mIsolatedUidRefCounts.put(isolatedUid, 1); + } + + List<Listener> listeners = mListeners; + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onIsolatedUidAdded(isolatedUid, parentUid); + } + } + + /** + * Handles the removal of an isolated UID by invoking + * {@link Listener#onBeforeIsolatedUidRemoved} on each registered listener and the releases + * the UID, see {@link #releaseIsolatedUid}. + */ + public void noteIsolatedUidRemoved(int isolatedUid, int parentUid) { + synchronized (this) { + int curUid = mIsolatedUids.get(isolatedUid, -1); + if (curUid != parentUid) { + Slog.wtf(TAG, "Attempt to remove an isolated UID " + isolatedUid + + " with the parent UID " + parentUid + + ". The registered parent UID is " + curUid); + return; + } + } + + List<Listener> listeners = mListeners; + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onBeforeIsolatedUidRemoved(isolatedUid, parentUid); + } + + releaseIsolatedUid(isolatedUid); + } + + /** + * Increments the ref count for an isolated uid. + * Call #releaseIsolatedUid to decrement. + */ + public void retainIsolatedUid(int uid) { + synchronized (this) { + final int refCount = mIsolatedUidRefCounts.get(uid, 0); + if (refCount <= 0) { + // Uid is not mapped or referenced + Slog.w(TAG, + "Attempted to increment ref counted of untracked isolated uid (" + uid + + ")"); + return; + } + mIsolatedUidRefCounts.put(uid, refCount + 1); + } + } + + /** + * Decrements the ref count for the given isolated UID. If the ref count drops to zero, + * removes the mapping and calls {@link Listener#onAfterIsolatedUidRemoved} on each registered + * listener. + */ + public void releaseIsolatedUid(int isolatedUid) { + int parentUid; + synchronized (this) { + final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1; + if (refCount > 0) { + // Isolated uid is still being tracked + mIsolatedUidRefCounts.put(isolatedUid, refCount); + return; + } + + final int idx = mIsolatedUids.indexOfKey(isolatedUid); + if (idx >= 0) { + parentUid = mIsolatedUids.valueAt(idx); + mIsolatedUids.removeAt(idx); + mIsolatedUidRefCounts.delete(isolatedUid); + } else { + Slog.w(TAG, "Attempted to remove untracked child uid (" + isolatedUid + ")"); + return; + } + } + + List<Listener> listeners = mListeners; + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onAfterIsolatedUidRemoved(isolatedUid, parentUid); + } + } + + /** + * Releases all isolated UIDs in the specified range, both ends inclusive. + */ + public void releaseUidsInRange(int startUid, int endUid) { + IntArray toRelease; + synchronized (this) { + int startIndex = mIsolatedUids.indexOfKey(startUid); + int endIndex = mIsolatedUids.indexOfKey(endUid); + + if (startIndex < 0) { + startIndex = ~startIndex; + } + + if (endIndex < 0) { + // In this ~endIndex is pointing just past where endUid would be, so we must -1. + endIndex = ~endIndex - 1; + } + + if (startIndex > endIndex) { + return; + } + + toRelease = new IntArray(endIndex - startIndex); + for (int i = startIndex; i <= endIndex; i++) { + toRelease.add(mIsolatedUids.keyAt(i)); + } + } + + for (int i = toRelease.size() - 1; i >= 0; i--) { + releaseIsolatedUid(toRelease.get(i)); + } + } + + /** + * Given an isolated UID, returns the corresponding owner UID. For a non-isolated + * UID, returns the UID itself. + */ + public int mapUid(int uid) { + synchronized (this) { + return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid); + } + } + + /** + * Dumps the current contents of the resolver for the sake of dumpsys. + */ + public void dump(PrintWriter pw) { + pw.println("Currently mapped isolated uids:"); + synchronized (this) { + final int numIsolatedUids = mIsolatedUids.size(); + for (int i = 0; i < numIsolatedUids; i++) { + final int isolatedUid = mIsolatedUids.keyAt(i); + final int ownerUid = mIsolatedUids.valueAt(i); + final int refs = mIsolatedUidRefCounts.get(isolatedUid); + pw.println(" " + isolatedUid + "->" + ownerUid + " (ref count = " + refs + ")"); + } + } + } +} 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 849836828d94..75e6faf97294 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2731,7 +2731,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Receive the splash screen data from shell, sending to client. * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy. */ - void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) { + void onCopySplashScreenFinish(@Nullable SplashScreenViewParcelable parcelable) { removeTransferSplashScreenTimeout(); final SurfaceControl windowAnimationLeash = (parcelable == null || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING 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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 34c7eee45f34..6f5c676187e1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3618,8 +3618,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * @hide */ @Override - public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable) - throws RemoteException { + public void onSplashScreenViewCopyFinished(int taskId, + @Nullable SplashScreenViewParcelable parcelable) + throws RemoteException { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS, "copySplashScreenViewFinish()"); synchronized (mGlobalLock) { 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 07cbd58744cb..50376fed2005 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1153,12 +1153,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplay = display; mDisplayId = display.getDisplayId(); mCurrentUniqueDisplayId = display.getUniqueId(); - mDisplayUpdater = new ImmediateDisplayUpdater(this); mOffTokenAcquirer = mRootWindowContainer.mDisplayOffTokenAcquirer; mWallpaperController = new WallpaperController(mWmService, this); mWallpaperController.resetLargestDisplay(display); display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); + mDisplayUpdater = new ImmediateDisplayUpdater(this); mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; @@ -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/ImmediateDisplayUpdater.java b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java index 72e8fcb05bb9..4af9013d7f4a 100644 --- a/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java @@ -30,6 +30,7 @@ public class ImmediateDisplayUpdater implements DisplayUpdater { public ImmediateDisplayUpdater(@NonNull DisplayContent displayContent) { mDisplayContent = displayContent; + mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); } @Override diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2bd732774ed3..522e7d205a00 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -627,7 +627,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void refreshSecureSurfaceState() { forAllWindows((w) -> { if (w.mHasSurface) { - w.mWinAnimator.setSecureLocked(w.isSecureLocked()); + w.setSecureLocked(w.isSecureLocked()); } }, true /* traverseTopToBottom */); } 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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 575ae69be12b..dd2b48bb5a3d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2327,8 +2327,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean wallpaperMayMove = win.mViewVisibility != viewVisibility && win.hasWallpaper(); wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0; - if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) { - winAnimator.mSurfaceController.setSecure(win.isSecureLocked()); + if ((flagChanges & FLAG_SECURE) != 0) { + win.setSecureLocked(win.isSecureLocked()); } final boolean wasVisible = win.isVisible(); 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/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6d6bcc88e8aa..e1f1f662c5aa 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -109,6 +109,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS; +import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT; @@ -177,6 +178,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; +import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.surfaceTrustedOverlay; import android.annotation.CallSuper; @@ -1195,6 +1197,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) { getPendingTransaction().setTrustedOverlay(mSurfaceControl, true); } + if (secureWindowState()) { + getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked()); + } } void updateTrustedOverlay() { @@ -6042,4 +6047,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Cancel any draw requests during a sync. return mPrepareSyncSeqId > 0; } + + void setSecureLocked(boolean isSecure) { + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName()); + if (secureWindowState()) { + if (mSurfaceControl == null) { + return; + } + getPendingTransaction().setSecure(mSurfaceControl, isSecure); + } else { + if (mWinAnimator.mSurfaceController == null + || mWinAnimator.mSurfaceController.mSurfaceControl == null) { + return; + } + getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl, + isSecure); + } + if (mDisplayContent != null) { + mDisplayContent.refreshImeSecureFlag(getSyncTransaction()); + } + mWmService.scheduleAnimationLocked(); + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 3aac816fcd7a..44cd23d037c6 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -44,6 +44,7 @@ import static com.android.server.wm.WindowManagerService.logWithStack; import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE; import static com.android.server.wm.WindowStateAnimatorProto.SURFACE; import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT; +import static com.android.window.flags.Flags.secureWindowState; import android.content.Context; import android.graphics.PixelFormat; @@ -286,8 +287,10 @@ class WindowStateAnimator { int flags = SurfaceControl.HIDDEN; final WindowManager.LayoutParams attrs = w.mAttrs; - if (w.isSecureLocked()) { - flags |= SurfaceControl.SECURE; + if (!secureWindowState()) { + if (w.isSecureLocked()) { + flags |= SurfaceControl.SECURE; + } } if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) { @@ -488,13 +491,6 @@ class WindowStateAnimator { mSurfaceController.setOpaque(isOpaque); } - void setSecureLocked(boolean isSecure) { - if (mSurfaceController == null) { - return; - } - mSurfaceController.setSecure(isSecure); - } - void setColorSpaceAgnosticLocked(boolean agnostic) { if (mSurfaceController == null) { return; diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index d348491b3d2a..4456a94ef510 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -24,7 +24,6 @@ import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN; @@ -152,24 +151,6 @@ class WindowSurfaceController { mService.scheduleAnimationLocked(); } - void setSecure(boolean isSecure) { - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, title); - - if (mSurfaceControl == null) { - return; - } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked"); - - final SurfaceControl.Transaction t = mAnimator.mWin.getPendingTransaction(); - t.setSecure(mSurfaceControl, isSecure); - - final DisplayContent dc = mAnimator.mWin.mDisplayContent; - if (dc != null) { - dc.refreshImeSecureFlag(t); - } - mService.scheduleAnimationLocked(); - } - void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title); 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/permission/OWNERS b/services/permission/OWNERS index e464038e68d9..487c992bbab4 100644 --- a/services/permission/OWNERS +++ b/services/permission/OWNERS @@ -1,5 +1,3 @@ #Bug component: 137825 -joecastro@google.com -ntmyren@google.com -zhanghai@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index f94a0d664a69..8f464d41792d 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -71,7 +71,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS // Not implemented because upgrades are handled automatically. } - override fun getNonDefaultUidModes(uid: Int): SparseIntArray { + override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray { return opNameMapToOpSparseArray(getUidModes(uid)) } @@ -79,7 +79,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return opNameMapToOpSparseArray(getPackageModes(packageName, userId)) } - override fun getUidMode(uid: Int, op: Int): Int { + override fun getUidMode(uid: Int, persistentDeviceId: String, op: Int): Int { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) @@ -92,7 +92,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map } - override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean { + override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) @@ -150,7 +150,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS // and we have our own persistence. } - override fun getForegroundOps(uid: Int): SparseBooleanArray { + override fun getForegroundOps(uid: Int, persistentDeviceId: String): SparseBooleanArray { return SparseBooleanArray().apply { getUidModes(uid)?.forEachIndexed { _, op, mode -> if (mode == AppOpsManager.MODE_FOREGROUND) { 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..1a3a6a388392 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); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index 92d1118d0f1e..4f672f81a9cb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -19,6 +19,7 @@ package com.android.server.appop; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT; import static android.app.AppOpsManager._NUM_OP; +import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -208,8 +209,8 @@ public class AppOpsUpgradeTest { private void assertSameModes(AppOpsCheckingServiceImpl testService, int op1, int op2) { for (int uid : testService.getUidsWithNonDefaultModes()) { assertEquals( - testService.getUidMode(uid, op1), - testService.getUidMode(uid, op2) + testService.getUidMode(uid, PERSISTENT_DEVICE_ID_DEFAULT, op1), + testService.getUidMode(uid, PERSISTENT_DEVICE_ID_DEFAULT, op2) ); } for (UserPackage pkg : testService.getPackagesWithNonDefaultModes()) { @@ -275,7 +276,9 @@ public class AppOpsUpgradeTest { } else { expectedMode = previousMode; } - int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); + int mode = + testService.getUidMode( + uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_SCHEDULE_EXACT_ALARM); assertEquals(expectedMode, mode); } } @@ -284,7 +287,9 @@ public class AppOpsUpgradeTest { int[] unrelatedUidsInFile = {10225, 10178}; for (int uid : unrelatedUidsInFile) { - int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); + int mode = + testService.getUidMode( + uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_SCHEDULE_EXACT_ALARM); assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM), mode); } } @@ -331,7 +336,9 @@ public class AppOpsUpgradeTest { final int uid = UserHandle.getUid(userId, appId); final int expectedMode = AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT); synchronized (testService) { - int mode = testService.getUidMode(uid, OP_USE_FULL_SCREEN_INTENT); + int mode = + testService.getUidMode( + uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_USE_FULL_SCREEN_INTENT); assertEquals(expectedMode, mode); } } 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/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java index 2003d04e1dbc..ca7de7c3f325 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java @@ -90,6 +90,10 @@ public class AggregatedPowerStatsTest { private AggregatedPowerStats prepareAggregatePowerStats() { AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); + + PowerStats ps = new PowerStats(mPowerComponentDescriptor); + stats.addPowerStats(ps, 0); + stats.addClockUpdate(1000, 456); stats.setDuration(789); @@ -100,7 +104,6 @@ public class AggregatedPowerStatsTest { stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000); - PowerStats ps = new PowerStats(mPowerComponentDescriptor); ps.stats[0] = 100; ps.stats[1] = 987; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 663af5da48d2..9c2834d31609 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -215,7 +215,7 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { - super(Clock.SYSTEM_CLOCK, null); + super(Clock.SYSTEM_CLOCK, null, null, null); mPowerProfile = new PowerProfile(context, true /* forTest */); SparseArray<int[]> cpusByPolicy = new SparseArray<>(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java index 55ffa1a15a6b..f9f32b2e7091 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java @@ -37,6 +37,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.os.BatteryStats; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.util.SparseArray; import android.util.SparseLongArray; @@ -97,6 +99,7 @@ public class BatteryStatsCpuTimesTest { BatteryStatsImpl.UserInfoProvider mUserInfoProvider; private MockClock mClocks; + private PowerStatsUidResolver mPowerStatsUidResolver; private MockBatteryStatsImpl mBatteryStatsImpl; private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; @@ -105,7 +108,9 @@ public class BatteryStatsCpuTimesTest { MockitoAnnotations.initMocks(this); mClocks = new MockClock(); - mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks) + Handler handler = new Handler(Looper.getMainLooper()); + mPowerStatsUidResolver = new PowerStatsUidResolver(); + mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks, null, handler, mPowerStatsUidResolver) .setTestCpuScalingPolicies() .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader) .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader) @@ -374,7 +379,7 @@ public class BatteryStatsCpuTimesTest { // PRECONDITIONS final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42); - mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid); + mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, ownerUid); final long[][] deltasUs = { {9379, 3332409833484L}, {493247, 723234}, {3247819, 123348} }; @@ -965,7 +970,7 @@ public class BatteryStatsCpuTimesTest { // PRECONDITIONS final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42); - mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid); + mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, ownerUid); final long[][] deltasMs = { {3, 12, 55, 100, 32}, {32483274, 232349349, 123, 2398, 0}, diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java index 5ebc6ca3f558..8d51592667c8 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -39,14 +39,22 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.UidTraffic; +import android.content.Context; +import android.os.BatteryConsumer; +import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BatteryUsageStats; import android.os.BluetoothBatteryStats; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; import android.os.Parcel; import android.os.WakeLockStats; import android.os.WorkSource; import android.util.SparseArray; import android.view.Display; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -65,6 +73,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; +import java.time.Instant; import java.util.List; @LargeTest @@ -93,6 +103,11 @@ public class BatteryStatsImplTest { private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStatsImpl; + private Handler mHandler; + private PowerStatsStore mPowerStatsStore; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + @Mock + private PowerStatsExporter mPowerStatsExporter; @Before public void setUp() { @@ -103,12 +118,23 @@ public class BatteryStatsImplTest { when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); when(mKernelWakelockReader.readKernelWakelockStats( any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats); - mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock) + HandlerThread bgThread = new HandlerThread("bg thread"); + bgThread.start(); + mHandler = new Handler(bgThread.getLooper()); + mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, null, mHandler) .setPowerProfile(mPowerProfile) .setCpuScalingPolicies(mCpuScalingPolicies) .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader) .setKernelWakelockReader(mKernelWakelockReader); + + final Context context = InstrumentationRegistry.getContext(); + File systemDir = context.getCacheDir(); + mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, + new AggregatedPowerStatsConfig()); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerStatsExporter, + mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore, + mMockClock); } @Test @@ -754,4 +780,76 @@ public class BatteryStatsImplTest { parcel.recycle(); return info; } + + @Test + public void storeBatteryUsageStatsOnReset() { + mBatteryStatsImpl.forceRecordAllHistory(); + + mMockClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli(); + mMockClock.realtime = 7654321; + + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.setOnBatteryLocked(mMockClock.realtime, mMockClock.uptime, true, + BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0); + // Will not save to PowerStatsStore because "saveBatteryUsageStatsOnReset" has not + // been called yet. + mBatteryStatsImpl.resetAllStatsAndHistoryLocked( + BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); + + mBatteryStatsImpl.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, + mPowerStatsStore); + + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.noteFlashlightOnLocked(42, mMockClock.realtime, mMockClock.uptime); + } + + mMockClock.realtime += 60000; + mMockClock.currentTime += 60000; + + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.noteFlashlightOffLocked(42, mMockClock.realtime, mMockClock.uptime); + } + + mMockClock.realtime += 60000; + mMockClock.currentTime += 60000; + + // Battery stats reset should have the side-effect of saving accumulated battery usage stats + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.resetAllStatsAndHistoryLocked( + BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + // Await completion + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + + List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); + assertThat(contents).hasSize(1); + + PowerStatsSpan.Metadata metadata = contents.get(0); + + PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), + BatteryUsageStatsSection.TYPE); + assertThat(span).isNotNull(); + + List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames(); + assertThat(timeFrames).hasSize(1); + assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321); + assertThat(timeFrames.get(0).duration).isEqualTo(120000); + + List<PowerStatsSpan.Section> sections = span.getSections(); + assertThat(sections).hasSize(1); + + PowerStatsSpan.Section section = sections.get(0); + assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE); + BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats(); + assertThat(bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo(60000); + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 7ef1a3fd0d83..24c67f83788b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -35,6 +35,8 @@ import android.app.usage.NetworkStatsManager; import android.os.BatteryStats; import android.os.BatteryStats.HistoryItem; import android.os.BatteryStats.Uid.Sensor; +import android.os.Handler; +import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; @@ -155,7 +157,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteStartWakeLocked_isolatedUid() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); int pid = 10; String name = "name"; @@ -165,7 +169,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -195,7 +199,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteStartWakeLocked_isolatedUidRace() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); int pid = 10; String name = "name"; @@ -205,7 +211,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -216,7 +222,7 @@ public class BatteryStatsNoteTest extends TestCase { bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); clocks.realtime = clocks.uptime = 150; - bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime); + uidResolver.releaseIsolatedUid(ISOLATED_UID); clocks.realtime = clocks.uptime = 220; bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName, @@ -237,8 +243,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); - + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); bi.setRecordAllHistoryLocked(true); bi.forceRecordAllHistory(); @@ -251,7 +258,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -290,8 +297,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); - + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); bi.setRecordAllHistoryLocked(true); bi.forceRecordAllHistory(); @@ -304,7 +312,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -314,7 +322,7 @@ public class BatteryStatsNoteTest extends TestCase { bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); clocks.realtime = clocks.uptime = 150; - bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime); + uidResolver.releaseIsolatedUid(ISOLATED_UID); clocks.realtime = clocks.uptime = 220; bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID); 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 b1da1fc88378..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 @@ -28,9 +28,7 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; +import android.os.ConditionVariable; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; @@ -40,7 +38,6 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistoryIterator; -import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import org.junit.Rule; @@ -72,10 +69,11 @@ public class BatteryUsageStatsProviderTest { BatteryStatsImpl batteryStats = prepareBatteryStats(); Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT); + provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT); final List<UidBatteryConsumer> uidBatteryConsumers = batteryUsageStats.getUidBatteryConsumers(); @@ -84,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( @@ -99,10 +106,11 @@ public class BatteryUsageStatsProviderTest { BatteryStatsImpl batteryStats = prepareBatteryStats(); Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats( + provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder() .includePowerComponents( new int[]{BatteryConsumer.POWER_COMPONENT_AUDIO}) @@ -204,10 +212,11 @@ public class BatteryUsageStatsProviderTest { } Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats( + provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build()); Parcel in = Parcel.obtain(); @@ -292,11 +301,11 @@ public class BatteryUsageStatsProviderTest { } Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider - provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats( + provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build()); Parcel parcel = Parcel.obtain(); @@ -352,27 +361,22 @@ public class BatteryUsageStatsProviderTest { @Test public void shouldUpdateStats() { - Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - mStatsRule.getBatteryStats()); - final List<BatteryUsageStatsQuery> queries = List.of( new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(), new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build() ); - mStatsRule.setTime(10500, 0); - assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse(); + assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries, + 10500, 10000)).isFalse(); - mStatsRule.setTime(11500, 0); - assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); + assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries, + 11500, 10000)).isTrue(); } @Test public void testAggregateBatteryStats() { Context context = InstrumentationRegistry.getContext(); BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock()); setTime(5 * MINUTE_IN_MS); synchronized (batteryStats) { @@ -381,14 +385,17 @@ public class BatteryUsageStatsProviderTest { PowerStatsStore powerStatsStore = new PowerStatsStore( new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), - new TestHandler(), null); + mStatsRule.getHandler(), null); + powerStatsStore.reset(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - batteryStats, powerStatsStore); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore, + mMockClock); - batteryStats.setBatteryResetListener(reason -> - powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(), - provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT))); + batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore); + synchronized (batteryStats) { + batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } synchronized (batteryStats) { batteryStats.noteFlashlightOnLocked(APP_UID, @@ -441,11 +448,16 @@ public class BatteryUsageStatsProviderTest { } setTime(95 * MINUTE_IN_MS); + // Await completion + ConditionVariable done = new ConditionVariable(); + mStatsRule.getHandler().post(done::open); + done.block(); + // Include the first and the second snapshot, but not the third or current BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS) .build(); - final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query); assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS); @@ -499,30 +511,19 @@ public class BatteryUsageStatsProviderTest { when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE)) .thenReturn(span1); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - batteryStats, powerStatsStore); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore, + mMockClock); BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .aggregateSnapshots(0, 3000) .build(); - final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query); assertThat(stats.getCustomPowerComponentNames()) .isEqualTo(batteryStats.getCustomEnergyConsumerNames()); assertThat(stats.getStatsDuration()).isEqualTo(1234); } - private static class TestHandler extends Handler { - TestHandler() { - super(Looper.getMainLooper()); - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - msg.getCallback().run(); - return true; - } - } - private static final Random sRandom = new Random(); /** diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 0b1095483dd0..e61dd0b41423 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -28,6 +28,8 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.HandlerThread; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; import android.util.SparseArray; @@ -57,6 +59,7 @@ public class BatteryUsageStatsRule implements TestRule { private final PowerProfile mPowerProfile; private final MockClock mMockClock = new MockClock(); private final MockBatteryStatsImpl mBatteryStats; + private final Handler mHandler; private BatteryUsageStats mBatteryUsageStats; private boolean mScreenOn; @@ -73,10 +76,13 @@ public class BatteryUsageStatsRule implements TestRule { } public BatteryUsageStatsRule(long currentTime, File historyDir) { + HandlerThread bgThread = new HandlerThread("bg thread"); + bgThread.start(); + mHandler = new Handler(bgThread.getLooper()); mContext = InstrumentationRegistry.getContext(); mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */)); mMockClock.currentTime = currentTime; - mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir); + mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler); mBatteryStats.setPowerProfile(mPowerProfile); mCpusByPolicy.put(0, new int[]{0, 1, 2, 3}); @@ -92,6 +98,10 @@ public class BatteryUsageStatsRule implements TestRule { return mMockClock; } + public Handler getHandler() { + return mHandler; + } + public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) { mPowerProfile.forceInitForTesting(mContext, xmlId); return this; 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/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java index 79084cc1b04d..8ca4ff6f86f5 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java @@ -192,7 +192,7 @@ public class CpuAggregatedPowerStatsProcessorTest { private static class MockPowerComponentAggregatedPowerStats extends PowerComponentAggregatedPowerStats { - private final CpuPowerStatsCollector.StatsArrayLayout mStatsLayout; + private final CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout; private final PowerStats.Descriptor mDescriptor; private HashMap<String, long[]> mDeviceStats = new HashMap<>(); private HashMap<String, long[]> mUidStats = new HashMap<>(); @@ -203,10 +203,10 @@ public class CpuAggregatedPowerStatsProcessorTest { MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config, boolean useEnergyConsumers) { super(config); - mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout(); + mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3); mStatsLayout.addDeviceSectionCpuTimeByCluster(2); - mStatsLayout.addDeviceSectionUptime(); + mStatsLayout.addDeviceSectionUsageDuration(); if (useEnergyConsumers) { mStatsLayout.addDeviceSectionEnergyConsumers(2); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java index bc211df5f143..64d5414bf66c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java @@ -19,6 +19,7 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -56,6 +57,9 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @SmallTest public class CpuPowerStatsCollectorTest { + private static final int ISOLATED_UID = 99123; + private static final int UID_1 = 42; + private static final int UID_2 = 99; private Context mContext; private final MockClock mMockClock = new MockClock(); private final HandlerThread mHandlerThread = new HandlerThread("test"); @@ -63,6 +67,8 @@ public class CpuPowerStatsCollectorTest { private PowerStats mCollectedStats; private PowerProfile mPowerProfile; @Mock + private PowerStatsUidResolver mUidResolver; + @Mock private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader; @Mock private PowerStatsInternal mPowerStatsInternal; @@ -76,6 +82,14 @@ public class CpuPowerStatsCollectorTest { mHandlerThread.start(); mHandler = mHandlerThread.getThreadHandler(); when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true); + when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { + int uid = invocation.getArgument(0); + if (uid == ISOLATED_UID) { + return UID_2; + } else { + return uid; + } + }); } @Test @@ -156,14 +170,14 @@ public class CpuPowerStatsCollectorTest { assertThat(descriptor.name).isEqualTo("cpu"); assertThat(descriptor.statsArrayLength).isEqualTo(13); assertThat(descriptor.uidStatsArrayLength).isEqualTo(5); - CpuPowerStatsCollector.StatsArrayLayout layout = - new CpuPowerStatsCollector.StatsArrayLayout(); + CpuPowerStatsCollector.CpuStatsArrayLayout layout = + new CpuPowerStatsCollector.CpuStatsArrayLayout(); layout.fromExtras(descriptor.extras); long[] deviceStats = new long[descriptor.statsArrayLength]; layout.setTimeByScalingStep(deviceStats, 2, 42); layout.setConsumedEnergy(deviceStats, 1, 43); - layout.setUptime(deviceStats, 44); + layout.setUsageDuration(deviceStats, 44); layout.setDevicePowerEstimate(deviceStats, 45); long[] uidStats = new long[descriptor.uidStatsArrayLength]; @@ -173,10 +187,10 @@ public class CpuPowerStatsCollectorTest { assertThat(layout.getCpuScalingStepCount()).isEqualTo(7); assertThat(layout.getTimeByScalingStep(deviceStats, 2)).isEqualTo(42); - assertThat(layout.getCpuClusterEnergyConsumerCount()).isEqualTo(2); + assertThat(layout.getEnergyConsumerCount()).isEqualTo(2); assertThat(layout.getConsumedEnergy(deviceStats, 1)).isEqualTo(43); - assertThat(layout.getUptime(deviceStats)).isEqualTo(44); + assertThat(layout.getUsageDuration(deviceStats)).isEqualTo(44); assertThat(layout.getDevicePowerEstimate(deviceStats)).isEqualTo(45); @@ -195,14 +209,15 @@ public class CpuPowerStatsCollectorTest { mockEnergyConsumers(); CpuPowerStatsCollector collector = createCollector(8, 0); - CpuPowerStatsCollector.StatsArrayLayout layout = - new CpuPowerStatsCollector.StatsArrayLayout(); + CpuPowerStatsCollector.CpuStatsArrayLayout layout = + new CpuPowerStatsCollector.CpuStatsArrayLayout(); layout.fromExtras(collector.getPowerStatsDescriptor().extras); mockKernelCpuStats(new long[]{1111, 2222, 3333}, new SparseArray<>() {{ - put(42, new long[]{100, 200}); - put(99, new long[]{300, 600}); + put(UID_1, new long[]{100, 200}); + put(UID_2, new long[]{100, 150}); + put(ISOLATED_UID, new long[]{200, 450}); }}, 0, 1234); mMockClock.uptime = 1000; @@ -219,19 +234,19 @@ public class CpuPowerStatsCollectorTest { assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(0); assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(0); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 0)) .isEqualTo(100); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 1)) .isEqualTo(200); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0)) .isEqualTo(300); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1)) .isEqualTo(600); mockKernelCpuStats(new long[]{5555, 4444, 3333}, new SparseArray<>() {{ - put(42, new long[]{123, 234}); - put(99, new long[]{345, 678}); + put(UID_1, new long[]{123, 234}); + put(ISOLATED_UID, new long[]{245, 528}); }}, 1234, 3421); mMockClock.uptime = 2000; @@ -249,13 +264,13 @@ public class CpuPowerStatsCollectorTest { // 700 * 1000 / 3500 assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(200); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 0)) .isEqualTo(23); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 1)) .isEqualTo(34); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0)) .isEqualTo(45); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1)) .isEqualTo(78); } @@ -282,9 +297,9 @@ public class CpuPowerStatsCollectorTest { private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies, - mPowerProfile, mHandler, mMockKernelCpuStatsReader, () -> mPowerStatsInternal, - () -> 3500, 60_000, mMockClock, defaultCpuPowerBrackets, - defaultCpuPowerBracketsPerEnergyConsumer); + mPowerProfile, mHandler, mMockKernelCpuStatsReader, mUidResolver, + () -> mPowerStatsInternal, () -> 3500, 60_000, mMockClock, + defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer); collector.addConsumer(stats -> mCollectedStats = stats); collector.setEnabled(true); return collector; @@ -375,8 +390,8 @@ public class CpuPowerStatsCollectorTest { } private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) { - CpuPowerStatsCollector.StatsArrayLayout layout = - new CpuPowerStatsCollector.StatsArrayLayout(); + CpuPowerStatsCollector.CpuStatsArrayLayout layout = + new CpuPowerStatsCollector.CpuStatsArrayLayout(); layout.fromExtras(collector.getPowerStatsDescriptor().extras); return layout.getScalingStepToPowerBracketMap(); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 4150972ab69a..fb71ac83a8d9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -61,16 +61,23 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } MockBatteryStatsImpl(Clock clock, File historyDirectory) { - super(clock, historyDirectory); + this(clock, historyDirectory, new Handler(Looper.getMainLooper())); + } + + MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler) { + this(clock, historyDirectory, handler, new PowerStatsUidResolver()); + } + + MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler, + PowerStatsUidResolver powerStatsUidResolver) { + super(clock, historyDirectory, handler, powerStatsUidResolver); initTimersAndCounters(); setMaxHistoryBuffer(128 * 1024); setExternalStatsSyncLocked(mExternalStatsSync); informThatAllExternalStatsAreFlushed(); - // A no-op handler. - mHandler = new Handler(Looper.getMainLooper()) { - }; + mHandler = handler; mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class); mKernelWakelockReader = null; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java index b52fc8a7c727..67049871f396 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java @@ -76,9 +76,18 @@ public class PowerStatsAggregatorTest { @Test public void stateUpdates() { + PowerStats.Descriptor descriptor = + new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, + new PersistableBundle()); + PowerStats powerStats = new PowerStats(descriptor); + mClock.currentTime = 1222156800000L; // An important date in world history mHistory.forceRecordAllHistory(); + powerStats.stats = new long[]{0}; + powerStats.uidStats.put(TEST_UID, new long[]{0}); + mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); + mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true); mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); @@ -87,10 +96,6 @@ public class PowerStatsAggregatorTest { advance(1000); - PowerStats.Descriptor descriptor = - new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, - new PersistableBundle()); - PowerStats powerStats = new PowerStats(descriptor); powerStats.stats = new long[]{10000}; powerStats.uidStats.put(TEST_UID, new long[]{1234}); mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); @@ -181,17 +186,21 @@ public class PowerStatsAggregatorTest { @Test public void incompatiblePowerStats() { + PowerStats.Descriptor descriptor = + new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, + new PersistableBundle()); + PowerStats powerStats = new PowerStats(descriptor); + mHistory.forceRecordAllHistory(); + powerStats.stats = new long[]{0}; + powerStats.uidStats.put(TEST_UID, new long[]{0}); + mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true); mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID, BatteryConsumer.PROCESS_STATE_FOREGROUND); advance(1000); - PowerStats.Descriptor descriptor = - new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, - new PersistableBundle()); - PowerStats powerStats = new PowerStats(descriptor); powerStats.stats = new long[]{10000}; powerStats.uidStats.put(TEST_UID, new long[]{1234}); mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java new file mode 100644 index 000000000000..3c482625b038 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java @@ -0,0 +1,316 @@ +/* + * 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.power.stats; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; + +import android.os.AggregateBatteryConsumer; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.PersistableBundle; +import android.os.UidBatteryConsumer; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.BatteryStatsHistory; +import com.android.internal.os.MonotonicClock; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class PowerStatsExporterTest { + + private static final int APP_UID1 = 42; + private static final int APP_UID2 = 84; + private static final double TOLERANCE = 0.01; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) + .setCpuScalingPolicy(0, new int[]{0}, new int[]{100}) + .setAveragePowerForCpuScalingPolicy(0, 360) + .setAveragePowerForCpuScalingStep(0, 0, 300) + .setCpuPowerBracketCount(1) + .setCpuPowerBracket(0, 0, 0); + + private MockClock mClock = new MockClock(); + private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); + private PowerStatsStore mPowerStatsStore; + private PowerStatsAggregator mPowerStatsAggregator; + private BatteryStatsHistory mHistory; + private CpuPowerStatsCollector.CpuStatsArrayLayout mCpuStatsArrayLayout; + private PowerStats.Descriptor mPowerStatsDescriptor; + + @Before + public void setup() { + File storeDirectory = new File(getContext().getCacheDir(), getClass().getSimpleName()); + clearDirectory(storeDirectory); + + AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) + .trackDeviceStates(AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates(AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new CpuAggregatedPowerStatsProcessor(mStatsRule.getPowerProfile(), + mStatsRule.getCpuScalingPolicies())); + + mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config); + mHistory = new BatteryStatsHistory(Parcel.obtain(), storeDirectory, 0, 10000, + mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, + mMonotonicClock, null); + mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory); + + mCpuStatsArrayLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); + mCpuStatsArrayLayout.addDeviceSectionCpuTimeByScalingStep(1); + mCpuStatsArrayLayout.addDeviceSectionCpuTimeByCluster(1); + mCpuStatsArrayLayout.addDeviceSectionPowerEstimate(); + mCpuStatsArrayLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0}); + mCpuStatsArrayLayout.addUidSectionPowerEstimate(); + PersistableBundle extras = new PersistableBundle(); + mCpuStatsArrayLayout.toExtras(extras); + + mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, + mCpuStatsArrayLayout.getDeviceStatsArrayLength(), + mCpuStatsArrayLayout.getUidStatsArrayLength(), extras); + } + + @Test + public void breakdownByProcState_fullRange() throws Exception { + BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + new String[0], /* includePowerModels */ false, + /* includeProcessStateData */ true, /* powerThreshold */ 0); + exportAggregatedPowerStats(builder, 1000, 10000); + + BatteryUsageStats actual = builder.build(); + String message = "Actual BatteryUsageStats: " + actual; + + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 13.5); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND, 7.47); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_BACKGROUND, 6.03); + + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 12.03); + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 12.03); + + actual.close(); + } + + @Test + public void breakdownByProcState_subRange() throws Exception { + BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + new String[0], /* includePowerModels */ false, + /* includeProcessStateData */ true, /* powerThreshold */ 0); + exportAggregatedPowerStats(builder, 3700, 6700); + + BatteryUsageStats actual = builder.build(); + String message = "Actual BatteryUsageStats: " + actual; + + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4); + + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 4.06); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND, 1.35); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_BACKGROUND, 2.70); + + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 11.33); + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 11.33); + + actual.close(); + } + + @Test + public void combinedProcessStates() throws Exception { + BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + new String[0], /* includePowerModels */ false, + /* includeProcessStateData */ false, /* powerThreshold */ 0); + exportAggregatedPowerStats(builder, 1000, 10000); + + BatteryUsageStats actual = builder.build(); + String message = "Actual BatteryUsageStats: " + actual; + + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 13.5); + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 12.03); + UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream() + .filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null); + // There shouldn't be any per-procstate data + assertThrows( + IllegalArgumentException.class, + () -> uidScope.getConsumedPower(new BatteryConsumer.Dimensions( + BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND))); + + + actual.close(); + } + + private void recordBatteryHistory() { + PowerStats powerStats = new PowerStats(mPowerStatsDescriptor); + long[] uidStats1 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()]; + powerStats.uidStats.put(APP_UID1, uidStats1); + long[] uidStats2 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()]; + powerStats.uidStats.put(APP_UID2, uidStats2); + + mHistory.forceRecordAllHistory(); + + mHistory.startRecordingHistory(1000, 1000, false); + mHistory.recordPowerStats(1000, 1000, powerStats); + mHistory.recordBatteryState(1000, 1000, 70, /* plugged */ false); + mHistory.recordStateStartEvent(1000, 1000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); + mHistory.recordProcessStateChange(1000, 1000, APP_UID1, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + mHistory.recordProcessStateChange(1000, 1000, APP_UID2, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 11111); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 10000); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 1111); + mHistory.recordPowerStats(1000, 1000, powerStats); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 12345); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 9876); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 2469); + mHistory.recordPowerStats(3000, 3000, powerStats); + + mPowerStatsAggregator.aggregatePowerStats(0, 3500, stats -> { + mPowerStatsStore.storeAggregatedPowerStats(stats); + }); + + mHistory.recordProcessStateChange(4000, 4000, APP_UID1, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + + mHistory.recordStateStopEvent(4000, 4000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 54321); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 14321); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 40000); + mHistory.recordPowerStats(6000, 6000, powerStats); + + mPowerStatsAggregator.aggregatePowerStats(3500, 6500, stats -> { + mPowerStatsStore.storeAggregatedPowerStats(stats); + }); + + mHistory.recordStateStartEvent(7000, 7000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); + mHistory.recordProcessStateChange(7000, 7000, APP_UID1, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + mHistory.recordProcessStateChange(7000, 7000, APP_UID2, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 23456); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 23456); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 0); + mHistory.recordPowerStats(8000, 8000, powerStats); + + assertThat(mPowerStatsStore.getTableOfContents()).hasSize(2); + } + + private void exportAggregatedPowerStats(BatteryUsageStats.Builder builder, + int monotonicStartTime, int monotonicEndTime) { + recordBatteryHistory(); + PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore, + mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0); + exporter.exportAggregatedPowerStats(builder, monotonicStartTime, monotonicEndTime); + } + + private void assertDevicePowerEstimate(String message, BatteryUsageStats bus, int componentId, + double expected) { + AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); + assertWithMessage(message).that(consumer.getConsumedPower(componentId)) + .isWithin(TOLERANCE).of(expected); + } + + private void assertAllAppsPowerEstimate(String message, BatteryUsageStats bus, int componentId, + double expected) { + AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + assertWithMessage(message).that(consumer.getConsumedPower(componentId)) + .isWithin(TOLERANCE).of(expected); + } + + private void assertUidPowerEstimate(String message, BatteryUsageStats bus, int uid, + int componentId, int processState, double expected) { + List<UidBatteryConsumer> uidScopes = bus.getUidBatteryConsumers(); + final UidBatteryConsumer uidScope = uidScopes.stream() + .filter(us -> us.getUid() == uid).findFirst().orElse(null); + assertWithMessage(message).that(uidScope).isNotNull(); + assertWithMessage(message).that(uidScope.getConsumedPower( + new BatteryConsumer.Dimensions(componentId, processState))) + .isWithin(TOLERANCE).of(expected); + } + + private void clearDirectory(File dir) { + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + clearDirectory(child); + } + child.delete(); + } + } + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java index 0e58787a6a9b..7257a94cbb9a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java @@ -27,9 +27,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.os.BatteryConsumer; -import android.os.BatteryManager; -import android.os.BatteryUsageStats; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; @@ -59,13 +56,13 @@ public class PowerStatsSchedulerTest { private MockClock mClock = new MockClock(); private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); private MockBatteryStatsImpl mBatteryStats; - private BatteryUsageStatsProvider mBatteryUsageStatsProvider; private PowerStatsScheduler mPowerStatsScheduler; private PowerProfile mPowerProfile; private PowerStatsAggregator mPowerStatsAggregator; private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; @Before + @SuppressWarnings("GuardedBy") public void setup() { final Context context = InstrumentationRegistry.getContext(); @@ -83,11 +80,10 @@ public class PowerStatsSchedulerTest { mPowerProfile = mock(PowerProfile.class); when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0); mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); mPowerStatsAggregator = mock(PowerStatsAggregator.class); mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock, - mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider); + mMonotonicClock, mHandler, mBatteryStats); } @Test @@ -176,70 +172,6 @@ public class PowerStatsSchedulerTest { } @Test - public void storeBatteryUsageStatsOnReset() { - mBatteryStats.forceRecordAllHistory(); - synchronized (mBatteryStats) { - mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true, - BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0); - } - - mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false); - - assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); - - mPowerStatsScheduler.start(true); - - synchronized (mBatteryStats) { - mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime); - } - - mClock.realtime += 60000; - mClock.currentTime += 60000; - - synchronized (mBatteryStats) { - mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime); - } - - mClock.realtime += 60000; - mClock.currentTime += 60000; - - // Battery stats reset should have the side-effect of saving accumulated battery usage stats - synchronized (mBatteryStats) { - mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - } - - // Await completion - ConditionVariable done = new ConditionVariable(); - mHandler.post(done::open); - done.block(); - - List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); - assertThat(contents).hasSize(1); - - PowerStatsSpan.Metadata metadata = contents.get(0); - - PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), - BatteryUsageStatsSection.TYPE); - assertThat(span).isNotNull(); - - List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames(); - assertThat(timeFrames).hasSize(1); - assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321); - assertThat(timeFrames.get(0).duration).isEqualTo(120000); - - List<PowerStatsSpan.Section> sections = span.getSections(); - assertThat(sections).hasSize(1); - - PowerStatsSpan.Section section = sections.get(0); - assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE); - BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats(); - assertThat(bus.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) - .isEqualTo(60000); - } - - @Test public void alignToWallClock() { // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15), diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java new file mode 100644 index 000000000000..60b2541fa7a8 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java @@ -0,0 +1,115 @@ +/* + * 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.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +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; + +@RunWith(AndroidJUnit4.class) +public class PowerStatsUidResolverTest { + + private final PowerStatsUidResolver mResolver = new PowerStatsUidResolver(); + @Mock + PowerStatsUidResolver.Listener mListener; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void addAndRemoveIsolatedUid() { + mResolver.addListener(mListener); + mResolver.noteIsolatedUidAdded(42, 314); + verify(mListener).onIsolatedUidAdded(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(314); + + mResolver.noteIsolatedUidRemoved(42, 314); + verify(mListener).onBeforeIsolatedUidRemoved(42, 314); + verify(mListener).onAfterIsolatedUidRemoved(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(42); + + verifyNoMoreInteractions(mListener); + } + + @Test + public void retainAndRemoveIsolatedUid() { + mResolver.addListener(mListener); + mResolver.noteIsolatedUidAdded(42, 314); + verify(mListener).onIsolatedUidAdded(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(314); + + mResolver.retainIsolatedUid(42); + + mResolver.noteIsolatedUidRemoved(42, 314); + verify(mListener).onBeforeIsolatedUidRemoved(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(314); + verifyNoMoreInteractions(mListener); + + mResolver.releaseIsolatedUid(42); + verify(mListener).onAfterIsolatedUidRemoved(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(42); + + verifyNoMoreInteractions(mListener); + } + + @Test + public void removeUidsInRange() { + mResolver.noteIsolatedUidAdded(1, 314); + mResolver.noteIsolatedUidAdded(2, 314); + mResolver.noteIsolatedUidAdded(3, 314); + mResolver.noteIsolatedUidAdded(4, 314); + mResolver.noteIsolatedUidAdded(6, 314); + mResolver.noteIsolatedUidAdded(8, 314); + mResolver.noteIsolatedUidAdded(10, 314); + + mResolver.addListener(mListener); + + mResolver.releaseUidsInRange(4, 4); // Single + verify(mListener).onAfterIsolatedUidRemoved(4, 314); + verifyNoMoreInteractions(mListener); + + // Now: [1, 2, 3, 6, 8, 10] + + mResolver.releaseUidsInRange(2, 3); // Inclusive + verify(mListener).onAfterIsolatedUidRemoved(2, 314); + verify(mListener).onAfterIsolatedUidRemoved(3, 314); + verifyNoMoreInteractions(mListener); + + // Now: [1, 6, 8, 10] + + mResolver.releaseUidsInRange(5, 9); // Exclusive + verify(mListener).onAfterIsolatedUidRemoved(6, 314); + verify(mListener).onAfterIsolatedUidRemoved(8, 314); + verifyNoMoreInteractions(mListener); + + // Now: [1, 10] + + mResolver.releaseUidsInRange(5, 9); // Empty + verifyNoMoreInteractions(mListener); + } +} 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/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index 6ae26585fab2..cd29c8057706 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -24,11 +24,13 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; +import android.app.usage.Flags; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.res.Configuration; +import android.os.PersistableBundle; import android.test.suitebuilder.annotation.SmallTest; import android.util.AtomicFile; import android.util.LongSparseArray; @@ -183,6 +185,17 @@ public class UsageStatsDatabaseTest { case Event.LOCUS_ID_SET: event.mLocusId = "locus" + (i % 7); //"random" locus break; + case Event.USER_INTERACTION: + if (Flags.userInteractionTypeApi()) { + // "random" user interaction extras. + PersistableBundle extras = new PersistableBundle(); + extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, + "fake.namespace.category" + (i % 13)); + extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, + "fakeaction" + (i % 13)); + event.mExtras = extras; + } + break; } mIntervalStats.addEvent(event); @@ -295,6 +308,18 @@ public class UsageStatsDatabaseTest { assertEquals(e1.mLocusIdToken, e2.mLocusIdToken, "Usage event " + debugId); break; + case Event.USER_INTERACTION: + if (Flags.userInteractionTypeApi()) { + PersistableBundle extras1 = e1.getExtras(); + PersistableBundle extras2 = e2.getExtras(); + assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY), + extras2.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY), + "Usage event " + debugId); + assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_ACTION), + extras2.getString(UsageStatsManager.EXTRA_EVENT_ACTION), + "Usage event " + debugId); + } + break; } // fallthrough case 4: // test fields added in version 4 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/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index bfb159f766d5..dce4818e8ed2 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -31,6 +31,7 @@ import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET; import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; +import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; @@ -42,7 +43,9 @@ import android.app.usage.EventList; import android.app.usage.EventStats; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; import android.content.res.Configuration; +import android.os.PersistableBundle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -575,6 +578,23 @@ public class IntervalStats { continue; } break; + case USER_INTERACTION: + if (event.mUserInteractionExtrasToken != null) { + String category = packagesTokenData.getString(packageToken, + event.mUserInteractionExtrasToken.mCategoryToken); + String action = packagesTokenData.getString(packageToken, + event.mUserInteractionExtrasToken.mActionToken); + if (TextUtils.isEmpty(category) || TextUtils.isEmpty(action)) { + this.events.remove(i); + dataOmitted = true; + continue; + } + event.mExtras = new PersistableBundle(); + event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, category); + event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action); + event.mUserInteractionExtrasToken = null; + } + break; } } if (dataOmitted) { @@ -692,13 +712,30 @@ public class IntervalStats { event.mPackage, event.mLocusId); } break; + case USER_INTERACTION: + if (event.mExtras != null && event.mExtras.size() != 0) { + final String category = event.mExtras.getString( + UsageStatsManager.EXTRA_EVENT_CATEGORY); + final String action = event.mExtras.getString( + UsageStatsManager.EXTRA_EVENT_ACTION); + if (!TextUtils.isEmpty(category) && !TextUtils.isEmpty(action)) { + event.mUserInteractionExtrasToken = + new Event.UserInteractionEventExtrasToken(); + event.mUserInteractionExtrasToken.mCategoryToken = + packagesTokenData.getTokenOrAdd(packageToken, event.mPackage, + category); + event.mUserInteractionExtrasToken.mActionToken = + packagesTokenData.getTokenOrAdd(packageToken, event.mPackage, + action); + } + } + break; } } } /** * Obfuscates the data in this instance of interval stats. - * * @hide */ public void obfuscateData(PackagesTokenData packagesTokenData) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java index 81387471f4d6..d86534582358 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java @@ -17,8 +17,10 @@ package com.android.server.usage; import android.app.usage.ConfigurationStats; import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event.UserInteractionEventExtrasToken; import android.app.usage.UsageStats; import android.content.res.Configuration; +import android.os.PersistableBundle; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -26,6 +28,8 @@ import android.util.SparseIntArray; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -282,6 +286,16 @@ final class UsageStatsProtoV2 { event.mLocusIdToken = proto.readInt( EventObfuscatedProto.LOCUS_ID_TOKEN) - 1; break; + case (int) EventObfuscatedProto.INTERACTION_EXTRAS: + try { + final long interactionExtrasToken = proto.start( + EventObfuscatedProto.INTERACTION_EXTRAS); + event.mUserInteractionExtrasToken = parseUserInteractionEventExtras(proto); + proto.end(interactionExtrasToken); + } catch (IOException e) { + Slog.e(TAG, "Unable to read some user interaction extras from proto.", e); + } + break; case ProtoInputStream.NO_MORE_FIELDS: return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event; } @@ -386,7 +400,7 @@ final class UsageStatsProtoV2 { } private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime, - final UsageEvents.Event event) throws IllegalArgumentException { + final UsageEvents.Event event) throws IOException, IllegalArgumentException { proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1); if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) { proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1); @@ -429,6 +443,12 @@ final class UsageStatsProtoV2 { event.mNotificationChannelIdToken + 1); } break; + case UsageEvents.Event.USER_INTERACTION: + if (event.mUserInteractionExtrasToken != null) { + writeUserInteractionEventExtras(proto, EventObfuscatedProto.INTERACTION_EXTRAS, + event.mUserInteractionExtrasToken); + } + break; } } @@ -703,6 +723,9 @@ final class UsageStatsProtoV2 { case (int) PendingEventProto.TASK_ROOT_CLASS: event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS); break; + case (int) PendingEventProto.EXTRAS: + event.mExtras = parsePendingEventExtras(proto, PendingEventProto.EXTRAS); + break; case ProtoInputStream.NO_MORE_FIELDS: // Handle default values for certain events types switch (event.mEventType) { @@ -757,7 +780,7 @@ final class UsageStatsProtoV2 { } private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event) - throws IllegalArgumentException { + throws IOException, IllegalArgumentException { proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage); if (event.mClass != null) { proto.write(PendingEventProto.CLASS_NAME, event.mClass); @@ -794,6 +817,11 @@ final class UsageStatsProtoV2 { event.mNotificationChannelId); } break; + case UsageEvents.Event.USER_INTERACTION: + if (event.mExtras != null && event.mExtras.size() != 0) { + writePendingEventExtras(proto, PendingEventProto.EXTRAS, event.mExtras); + } + break; } } @@ -888,4 +916,52 @@ final class UsageStatsProtoV2 { proto.end(token); } } + + private static UserInteractionEventExtrasToken parseUserInteractionEventExtras( + ProtoInputStream proto) throws IOException { + UserInteractionEventExtrasToken interactionExtrasToken = + new UserInteractionEventExtrasToken(); + while (true) { + switch (proto.nextField()) { + case (int) ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN: + interactionExtrasToken.mCategoryToken = proto.readInt( + ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN) - 1; + break; + case (int) ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN: + interactionExtrasToken.mActionToken = proto.readInt( + ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN) - 1; + break; + case ProtoInputStream.NO_MORE_FIELDS: + return interactionExtrasToken; + } + } + } + + static void writeUserInteractionEventExtras(ProtoOutputStream proto, long fieldId, + UserInteractionEventExtrasToken interactionExtras) { + final long token = proto.start(fieldId); + proto.write(ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN, + interactionExtras.mCategoryToken + 1); + proto.write(ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN, + interactionExtras.mActionToken + 1); + proto.end(token); + } + + /** + * Populates the extra details for pending interaction event from the protobuf stream. + */ + private static PersistableBundle parsePendingEventExtras(ProtoInputStream proto, long fieldId) + throws IOException { + return PersistableBundle.readFromStream(new ByteArrayInputStream(proto.readBytes(fieldId))); + } + + /** + * Write the extra details for pending interaction event to a protobuf stream. + */ + static void writePendingEventExtras(ProtoOutputStream proto, long fieldId, + PersistableBundle eventExtras) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + eventExtras.writeToStream(baos); + proto.write(fieldId, baos.toByteArray()); + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 4c56f33af430..0e1e0c8e3ad3 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -83,6 +83,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -91,6 +92,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -193,6 +195,11 @@ public class UsageStatsService extends SystemService implements private static final char TOKEN_DELIMITER = '/'; + // The maximum length for extras {@link UsageStatsManager#EXTRA_EVENT_CATEGORY}, + // {@link UsageStatsManager#EXTRA_EVENT_ACTION} in a {@link UsageEvents.Event#mExtras}. + // The value will be truncated at this limit. + private static final int MAX_TEXT_LENGTH = 127; + // Handler message types. static final int MSG_REPORT_EVENT = 0; static final int MSG_FLUSH_TO_DISK = 1; @@ -1814,6 +1821,13 @@ public class UsageStatsService extends SystemService implements mHandler.removeMessages(MSG_FLUSH_TO_DISK); } + private String getTrimmedString(String input) { + if (input != null && input.length() > MAX_TEXT_LENGTH) { + return input.substring(0, MAX_TEXT_LENGTH); + } + return input; + } + /** * Called by the Binder stub. */ @@ -2253,6 +2267,32 @@ public class UsageStatsService extends SystemService implements } } + private void reportUserInteractionInnerHelper(String packageName, @UserIdInt int userId, + PersistableBundle extras) { + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportUserInteraction"); + } + } else { + if (!isCallingUidSystem()) { + throw new SecurityException("Only system is allowed to call" + + " reportUserInteraction"); + } + } + + // Verify if this package exists before reporting an event for it. + if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) { + throw new IllegalArgumentException("Package " + packageName + "not exist!"); + } + + final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); + event.mPackage = packageName; + event.mExtras = extras; + reportEventOrAddToQueue(userId, event); + } + @Override public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime, String callingPackage, int userId) { @@ -2686,23 +2726,36 @@ public class UsageStatsService extends SystemService implements @Override public void reportUserInteraction(String packageName, int userId) { + reportUserInteractionInnerHelper(packageName, userId, null); + } + + @Override + public void reportUserInteractionWithBundle(String packageName, @UserIdInt int userId, + PersistableBundle extras) { Objects.requireNonNull(packageName); - if (Flags.reportUsageStatsPermission()) { - if (!canReportUsageStats()) { - throw new SecurityException( - "Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportUserInteraction"); - } - } else { - if (!isCallingUidSystem()) { - throw new SecurityException("Only system is allowed to call" - + " reportUserInteraction"); - } + if (extras == null || extras.size() == 0) { + throw new IllegalArgumentException("Emtry extras!"); } - final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); - event.mPackage = packageName; - reportEventOrAddToQueue(userId, event); + // Only category/action are allowed now, other unknown keys will be trimmed. + // Also, empty category/action is not meanful. + String category = extras.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY); + if (TextUtils.isEmpty(category)) { + throw new IllegalArgumentException("Empty " + + UsageStatsManager.EXTRA_EVENT_CATEGORY); + } + String action = extras.getString(UsageStatsManager.EXTRA_EVENT_ACTION); + if (TextUtils.isEmpty(action)) { + throw new IllegalArgumentException("Empty " + + UsageStatsManager.EXTRA_EVENT_ACTION); + } + + PersistableBundle extrasCopy = new PersistableBundle(); + extrasCopy.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, + getTrimmedString(category)); + extrasCopy.putString(UsageStatsManager.EXTRA_EVENT_ACTION, getTrimmedString(action)); + + reportUserInteractionInnerHelper(packageName, userId, extrasCopy); } @Override @@ -3160,6 +3213,24 @@ public class UsageStatsService extends SystemService implements } @Override + public void reportUserInteractionEvent(@NonNull String pkgName, @UserIdInt int userId, + @NonNull PersistableBundle extras) { + if (extras != null && extras.size() != 0) { + // Truncate the value if necessary. + String category = extras.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY); + String action = extras.getString(UsageStatsManager.EXTRA_EVENT_ACTION); + extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, + getTrimmedString(category)); + extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, getTrimmedString(action)); + } + + Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); + event.mPackage = pkgName; + event.mExtras = extras; + reportEventOrAddToQueue(userId, event); + } + + @Override public boolean isAppIdle(String packageName, int uidForAppId, int userId) { return mAppStandby.isAppIdleFiltered(packageName, uidForAppId, userId, SystemClock.elapsedRealtime()); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 9b67ab6d99c1..3bc77526a967 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -1110,6 +1110,10 @@ class UserUsageStatsService { if (event.mNotificationChannelId != null) { pw.printPair("channelId", event.mNotificationChannelId); } + + if ((event.mEventType == Event.USER_INTERACTION) && (event.mExtras != null)) { + pw.print(event.mExtras.toString()); + } pw.printHexPair("flags", event.mFlags); pw.println(); } 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/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java index d075b5f01ef9..5434c82b07bd 100644 --- a/tests/Input/src/com/android/test/input/InputDeviceTest.java +++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java @@ -51,6 +51,7 @@ public class InputDeviceTest { assertEquals(device.getName(), outDevice.getName()); assertEquals(device.getVendorId(), outDevice.getVendorId()); assertEquals(device.getProductId(), outDevice.getProductId()); + assertEquals(device.getDeviceBus(), outDevice.getDeviceBus()); assertEquals(device.getDescriptor(), outDevice.getDescriptor()); assertEquals(device.isExternal(), outDevice.isExternal()); assertEquals(device.getSources(), outDevice.getSources()); @@ -79,6 +80,7 @@ public class InputDeviceTest { .setName("Test Device " + DEVICE_ID) .setVendorId(44) .setProductId(45) + .setDeviceBus(3) .setDescriptor("descriptor") .setExternal(true) .setSources(InputDevice.SOURCE_HDMI) 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); } } |