Merge "De-deprecate AttributionSource.setNext" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 158d914..e6c94d8 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.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.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.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.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 @@
* 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 @@
/**
* 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 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 @@
}
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 @@
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 @@
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 @@
}
}
- 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 d2150b8..8381d1a 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.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 @@
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 @@
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 @@
// 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 @@
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 @@
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 a4043dd..61096b9 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.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 @@
}
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 @@
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 91a291f..69e5736 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.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 @@
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 @@
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 e0fa300..a525863 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9342,6 +9342,7 @@
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 @@
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
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ce5752f..fc23f9b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -258,6 +258,7 @@
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";
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 6a03c17..ce1d43d 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -180,6 +180,11 @@
@Override
public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
mAccessibilityManager.injectInputEventToInputFilter(event);
}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index ebd5d64..cf19178 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.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 @@
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 aa32392..7bc6cb9 100644
--- a/core/java/android/app/usage/ParcelableUsageEventList.java
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -223,6 +223,7 @@
event.mContentAnnotations = null;
event.mNotificationChannelId = null;
event.mLocusId = null;
+ event.mExtras = null;
switch (event.mEventType) {
case Event.CONFIGURATION_CHANGE -> {
@@ -237,6 +238,11 @@
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 @@
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 6c7eba0..0ae00cd 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.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 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 @@
}
/**
+ * 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 @@
mBucketAndReason = orig.mBucketAndReason;
mNotificationChannelId = orig.mNotificationChannelId;
mLocusId = orig.mLocusId;
+ mExtras = orig.mExtras;
}
}
@@ -987,6 +1022,14 @@
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 +1079,7 @@
eventOut.mContentAnnotations = null;
eventOut.mNotificationChannelId = null;
eventOut.mLocusId = null;
+ eventOut.mExtras = null;
switch (eventOut.mEventType) {
case Event.CONFIGURATION_CHANGE:
@@ -1059,6 +1103,11 @@
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 4f1c993..85d223d 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -28,6 +28,7 @@
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.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 @@
@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 @@
* 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 @@
* 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 @@
* 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 @@
}
/**
+ * 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 c6012bb..51a7f1c 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -52,6 +52,7 @@
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;
@@ -562,6 +563,40 @@
});
}
+ 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 @@
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 @@
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 @@
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 102cbf3..3520c0b 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -246,4 +246,10 @@
*/
@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 da8277c..9492a62 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -19,6 +19,7 @@
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 @@
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 beee86f..52afa4e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCamera.java
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -66,15 +66,8 @@
@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 @@
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/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index dfc27ca..507e814 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.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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index dc86e3f5..5cbc18e 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -56,4 +56,11 @@
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 ff6ec29..8f18c5f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14975,6 +14975,38 @@
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/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0063d13..886727e 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -490,16 +490,32 @@
/**
* Notify changes to activity state changes on certain subscription.
*
+ * @param subId for which data activity state changed.
+ * @param dataActivityType indicates the latest data activity type e.g. {@link
+ * TelephonyManager#DATA_ACTIVITY_IN}
+ */
+ public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
+ try {
+ sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType);
+ } catch (RemoteException ex) {
+ // system process is dead
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify changes to activity state changes on certain subscription.
+ *
* @param slotIndex for which data activity changed. Can be derived from subId except
* when subId is invalid.
* @param subId for which data activity state changed.
- * @param dataActivityType indicates the latest data activity type e.g, {@link
+ * @param dataActivityType indicates the latest data activity type e.g. {@link
* TelephonyManager#DATA_ACTIVITY_IN}
*/
public void notifyDataActivityChanged(int slotIndex, int subId,
@DataActivityType int dataActivityType) {
try {
- sRegistry.notifyDataActivityForSubscriber(slotIndex, subId, dataActivityType);
+ sRegistry.notifyDataActivityForSubscriberWithSlot(slotIndex, subId, dataActivityType);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 23d0de3..cbbe785 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -503,9 +503,6 @@
// 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());
@@ -1590,54 +1587,30 @@
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.
}
/**
@@ -4513,39 +4486,6 @@
}
/**
- * 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 d591f89..d58c07d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33018,7 +33018,7 @@
}
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 7a6c292..cac5387 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -991,7 +991,7 @@
// 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/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl
index e177731..c6bd20c 100644
--- a/core/java/android/webkit/IWebViewUpdateService.aidl
+++ b/core/java/android/webkit/IWebViewUpdateService.aidl
@@ -79,4 +79,9 @@
* 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 a919c00..1acebf4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1054,8 +1054,7 @@
}
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 @@
@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 @@
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 @@
@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 @@
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 @@
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 @@
}
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 @@
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/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 7c4252e..6b074a6 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_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 @@
}
/**
+ * 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 f5fe12e..e55c641 100644
--- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
+++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
@@ -37,22 +37,11 @@
* @return The highest refresh rate
*/
public static float findHighestRefreshRateForDefaultDisplay(Context context) {
- return findHighestRefreshRate(context, Display.DEFAULT_DISPLAY);
- }
-
- /**
- * Find the highest refresh rate among all the modes of the specified display.
- *
- * @param context The context
- * @param displayId The display ID
- * @return The highest refresh rate
- */
- public static float findHighestRefreshRate(Context context, int displayId) {
final DisplayManager dm = context.getSystemService(DisplayManager.class);
- final Display display = dm.getDisplay(displayId);
+ final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
if (display == null) {
- Log.w(TAG, "No valid display device with ID = " + displayId);
+ Log.w(TAG, "No valid default display device");
return DEFAULT_REFRESH_RATE;
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index dadeb2b..aab2242 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -60,7 +60,8 @@
@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 440a332..f365dbb 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -462,9 +462,4 @@
],
},
},
-
- // Workaround Clang LTO crash.
- lto: {
- never: true,
- },
}
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index 2b74220..11b367b 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -92,8 +92,24 @@
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 d5cf60d..c242c9c 100644
--- a/core/proto/android/server/usagestatsservice_v2.proto
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -110,6 +110,7 @@
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 @@
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 @@
// 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 4d208c6..0021640 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/res/values/config.xml b/core/res/res/values/config.xml
index 98897d8..8bbc809 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.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 017688a..3d43004 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2078,6 +2078,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" />
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 5a202c5..9dce899 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 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 @@
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 @@
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 083e37a..fae7148 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 @@
"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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d36ac39..2237ba1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -529,6 +529,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
+ "-1582845629": {
+ "message": "Starting animation on %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+ },
"-1575977269": {
"message": "Skipping %s: mismatch root %s",
"level": "DEBUG",
@@ -925,6 +931,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "-1243510456": {
+ "message": "Dim animation requested: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+ },
"-1237827119": {
"message": "Schedule remove starting %s startingWindow=%s animate=%b Callers=%s",
"level": "VERBOSE",
@@ -1171,6 +1183,12 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
+ "-1028213464": {
+ "message": "%s skipping animation and directly setting alpha=%f, blur=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+ },
"-1022146708": {
"message": "Skipping %s: mismatch activity type",
"level": "DEBUG",
@@ -1795,12 +1813,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-504637678": {
- "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_DIMMER",
- "at": "com\/android\/server\/wm\/SmoothDimmer.java"
- },
"-503656156": {
"message": "Update process config of %s to new config %s",
"level": "VERBOSE",
@@ -4027,12 +4039,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1620751818": {
- "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_DIMMER",
- "at": "com\/android\/server\/wm\/SmoothDimmer.java"
- },
"1621562070": {
"message": " startWCT=%s",
"level": "VERBOSE",
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 5e16bce..dd703f5 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -33,7 +33,6 @@
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 @@
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));
}
- return Objects.requireNonNull(mBinder);
+ if (mBinder == null) {
+ throw new IllegalStateException(
+ "Could not connect to Keystore service. Keystore may have crashed or not been"
+ + " initialized");
+ }
+ Binder.allowBlocking(mBinder.asBinder());
+ return mBinder;
}
void delete(KeyDescriptor descriptor) throws KeyStoreException {
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 ef8393c..35a1fa0 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 @@
@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 8cbcde3..53ec201 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.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 @@
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 @@
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 @@
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 @@
.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 @@
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/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index 6429b00..fafd37b 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"/>
@@ -76,6 +79,8 @@
value="appops set com.android.shell android:mock_location deny"/>
</target_preparer>
+ <target_preparer class="com.android.csuite.core.AppCrawlTesterHostPreparer"/>
+
<!-- Use app crawler to log into Netflix -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
new file mode 100644
index 0000000..b91d6f9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
@@ -0,0 +1,37 @@
+/*
+ * 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.wm.shell;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+
+/**
+ * Basic test handler that immediately executes anything that is posted on it.
+ */
+public class TestHandler extends Handler {
+ public TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ dispatchMessage(msg);
+ return true;
+ }
+}
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 4afb29e..d7c4610 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.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 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 @@
Transitions mTransitions;
@Mock
Looper mViewLooper;
- @Mock
- Handler mViewHandler;
+ TestHandler mViewHandler;
SurfaceSession mSession;
SurfaceControl mLeash;
@@ -112,7 +113,7 @@
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 @@
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/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index c471a27..c59b8c5 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -5,4 +5,11 @@
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"
+}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index be881b1..5237cae 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -27,9 +27,11 @@
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.Entry;
import java.util.Objects;
@@ -52,6 +54,9 @@
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,7 +71,8 @@
@Override
public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) {
- mLoudnessListenerMgr.callListeners(listener ->
+ mLoudnessListenerMgr.callListeners(listener -> {
+ synchronized (mLock) {
mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
// send the appropriate bundle for the user to update
if (lcConfig.getAssignedTrackPiid() == piid) {
@@ -95,9 +101,10 @@
}
}
}
-
return lcConfig;
- }));
+ });
+ }
+ });
}
private static Bundle filterLoudnessParams(Bundle bundle) {
@@ -130,21 +137,33 @@
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");
+ }
}
}
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index fe26dc3..991fe41 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -16,10 +16,12 @@
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 42f1207..325d3f8 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.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.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 83183b5..3ef65b0 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.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 @@
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 0000000..f063074
--- /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 3471070..0000000
--- 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 d4bca2a..f1f1f7c 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
similarity index 93%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt
rename to packages/CredentialManager/shared/src/com/android/credentialmanager/model/CredentialType.kt
index cc92f60..3f85192 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
similarity index 92%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
rename to packages/CredentialManager/shared/src/com/android/credentialmanager/model/EntryInfo.kt
index ee36989..6d59f11 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/Password.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt
deleted file mode 100644
index 2fe4fd5..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt
+++ /dev/null
@@ -1,26 +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.model
-
-import android.credentials.ui.Entry
-import androidx.credentials.provider.PasswordCredentialEntry
-
-data class Password(
- val providerId: String,
- val entry: Entry,
- val passwordCredentialEntry: PasswordCredentialEntry,
-)
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 2289ed7..7636462 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 @@
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 0000000..d6189eb
--- /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/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt
similarity index 61%
copy from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
copy to packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt
index ee36989..7ee50d7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-package com.android.credentialmanager.common
+package com.android.credentialmanager.model.creation
import android.app.PendingIntent
import android.content.Intent
+import com.android.credentialmanager.model.EntryInfo
-open class BaseEntry (
- val providerId: String,
- val entryKey: String,
- val entrySubkey: String,
- val pendingIntent: PendingIntent?,
- val fillInIntent: Intent?,
- val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
+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 0000000..d9eee86
--- /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 0000000..01c394f
--- /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 0000000..9725881
--- /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 0000000..6da4146
--- /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/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt
similarity index 61%
copy from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
copy to packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt
index ee36989..a68bf74 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-package com.android.credentialmanager.common
+package com.android.credentialmanager.model.get
import android.app.PendingIntent
import android.content.Intent
+import com.android.credentialmanager.model.EntryInfo
-open class BaseEntry (
- val providerId: String,
- val entryKey: String,
- val entrySubkey: String,
- val pendingIntent: PendingIntent?,
- val fillInIntent: Intent?,
- val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
+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 bce86c4..6c5a984 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.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 @@
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 @@
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 @@
/**************************************************************************/
/***** 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 @@
)
}
- 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 f0fa6c5..fc3970d 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.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 @@
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 @@
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 @@
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 @@
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 281696d..20d2f09 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.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 @@
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 @@
}
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 @@
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 @@
presentationBuilder.build())
.build())
.setAuthentication(pendingIntent.intentSender)
- .setAuthenticationExtras(primaryEntry.fillInIntent.extras)
+ .setAuthenticationExtras(fillInIntent.extras)
.build())
datasetAdded = true
}
@@ -322,8 +323,8 @@
}
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 d45b6f6..14a9165 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.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.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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 e9e8c2e..8b0ba87 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 @@
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 @@
* 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 72d030b..4ed84b9 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.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.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 @@
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 AllSignInOptionCard(
providerInfoList: List<ProviderInfo>,
providerDisplayInfo: ProviderDisplayInfo,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
onBackButtonClicked: () -> Unit,
onCancel: () -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
@@ -436,7 +441,7 @@
@Composable
fun ActionChips(
providerInfoList: List<ProviderInfo>,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
isFirstSection: Boolean,
) {
val actionChips = providerInfoList.flatMap { it.actionEntryList }
@@ -460,7 +465,7 @@
@Composable
fun RemoteEntryCard(
remoteEntry: RemoteEntryInfo,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
isFirstSection: Boolean,
) {
CredentialListSectionHeader(
@@ -486,7 +491,7 @@
@Composable
fun LockedCredentials(
authenticationEntryList: List<AuthenticationEntryInfo>,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
isFirstSection: Boolean,
) {
CredentialListSectionHeader(
@@ -508,7 +513,7 @@
@Composable
fun PerUserNameCredentials(
perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
isFirstSection: Boolean,
) {
CredentialListSectionHeader(
@@ -532,7 +537,7 @@
@Composable
fun CredentialEntryRow(
credentialEntryInfo: CredentialEntryInfo,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
enforceOneLine: Boolean = false,
onTextLayout: (TextLayoutResult) -> Unit = {},
) {
@@ -571,7 +576,7 @@
@Composable
fun AuthenticationEntryRow(
authenticationEntryInfo: AuthenticationEntryInfo,
- onEntrySelected: (BaseEntry) -> Unit,
+ onEntrySelected: (EntryInfo) -> Unit,
enforceOneLine: Boolean = false,
) {
Entry(
@@ -596,7 +601,7 @@
@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 447a9d2..46bebc4 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 @@
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 @@
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 @@
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 @@
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 @@
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 6ededf3..d8a6019 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 f2f878e..14b992a 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 @@
// 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 c28df3e8..b64f581 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 @@
}
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 fb72c54..26bee1f 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.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 @@
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 @@
_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 @@
fun onOKClick() {
_uiState.value = SinglePasswordScreenUiState.PasswordSelected(
- intentSenderRequest = password.getIntentSenderRequest()
+ intentSenderRequest = entryInfo.getIntentSenderRequest()
)
}
@@ -94,9 +95,9 @@
) {
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 @@
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/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 19d74b3..7b17cbd 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 @@
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/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 9af88c3b..215ead3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -18,6 +18,7 @@
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 @@
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/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 4871ef3..010a6ce 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -17,7 +17,6 @@
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 4b3acbf..e70114f 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 2b5fcd8..e6f61a8 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 @@
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 androidx.annotation.ColorInt;
-import com.android.settingslib.utils.BuildCompatUtils;
import com.android.settingslib.widget.mainswitch.R;
import java.util.ArrayList;
@@ -72,11 +73,18 @@
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 @@
* 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 @@
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 @@
}
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/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index efed8c3..85e8769 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -235,6 +235,7 @@
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/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0480b9d..0c89a5d 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/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 1762065..3e84597 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -164,3 +164,9 @@
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/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 fab290d..7b2ac90 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.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 @@
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 @@
@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 @@
@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 820c056..8791877 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.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 @@
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 @@
@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 @@
@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/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 9c08f5e..355e75d 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 af29cad..50241cd 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" />
+ android:textDirection="locale"
+ android:textAlignment="viewStart"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:visibility="gone" />
- <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" />
-
- <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/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 1ac4163..ab23564 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.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 @@
// 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 @@
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 11a5d8b..3defec5 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 @@
): 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/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 715fb17..4cddb9c 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 @@
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 @@
* mechanism like face or trust manager. This returns `false` whenever the lockscreen has been
* dismissed.
*
+ * A value of `null` is meaningless and is used as placeholder while the actual value is still
+ * being loaded in the background.
+ *
* Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
* UI.
*/
- val canSwipeToEnter =
+ val canSwipeToEnter: StateFlow<Boolean?> =
combine(
// This is true when the user has chosen to show the lockscreen but has not made it
// secure.
authenticationInteractor.authenticationMethod.map {
it == AuthenticationMethodModel.None && repository.isLockscreenEnabled()
},
- passivelyAuthenticated,
+ isPassivelyAuthenticated,
isDeviceEntered
- ) { isSwipeAuthMethod, passivelyAuthenticated, isDeviceEntered ->
- (isSwipeAuthMethod || passivelyAuthenticated) && !isDeviceEntered
+ ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered ->
+ (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered
}
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
- initialValue = false,
+ // Starts as null to prevent downstream collectors from falsely assuming that the
+ // user can or cannot swipe to enter the device while the real value is being loaded
+ // from upstream data sources.
+ initialValue = null,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2a0d6a8..0c24752 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 @@
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/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a2e930c..9f46e22 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
@@ -287,6 +287,10 @@
return
}
+ if (smartSpaceView != null) {
+ parentView.removeView(smartSpaceView)
+ }
+
smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
val topPadding: Int =
@@ -362,12 +366,13 @@
),
)
+ setUpUdfps(previewContext, rootView)
+
disposables.add(
PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
if (keyguardBottomAreaRefactor()) {
setupShortcuts(keyguardRootView)
}
- setUpUdfps(previewContext, rootView)
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
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 a321eef..6f5dea3 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 @@
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 @@
* 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 @@
*
* 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 @@
}
}
- /**
- * 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/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
index 94137c8..4a34276 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 @@
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 5bdb592..db3cf0f 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 @@
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 @@
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 @@
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 @@
}
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 34c2aba..5d5e747 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.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 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 @@
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 @@
)
)
}
- ?: dialog!!.show()
+ ?: dialog.show()
- updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
}
@@ -102,7 +101,7 @@
bluetoothStateInteractor.bluetoothStateUpdate
.filterNotNull()
.onEach {
- dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it))
+ dialog.onBluetoothStateUpdated(it, getSubtitleResId(it))
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(
@@ -129,7 +128,7 @@
.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 @@
}
.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 @@
logger,
context
)
- .apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } }
+ .apply { SystemUIDialog.registerDismissListener(this) { cancelJob() } }
}
override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
@@ -188,15 +187,13 @@
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 76fbf8e..fcd45a6 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 @@
)
}
- internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
- logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
+ 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)
+ 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 c390695b..cfb5442 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 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 0000000..869f6f32
--- /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 0000000..ca5302e
--- /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 83767aa..d956fde 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.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 @@
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 0000000..351bba5
--- /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 b2b22646..881a6bd 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 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 8e53723..7e7034d 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 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 f9e0b16..23e0cb6 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 @@
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 f3f9c91..1c5330e 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
@@ -146,19 +146,21 @@
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 @@
// 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 @@
} 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 d14ef35..dbb58a3 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.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 @@
class SceneContainerFlagsImpl
@AssistedInject
constructor(
+ @Application private val context: Context,
private val featureFlagsClassic: FeatureFlagsClassic,
@Assisted private val isComposeAvailable: Boolean,
) : SceneContainerFlags {
@@ -80,7 +84,11 @@
),
) +
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 @@
}
}
+ 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/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 2a071de..0065db3 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 @@
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/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 0e83c78..e486457 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 @@
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/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 64f61d9..8eda96f 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.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 @@
* 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 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
- mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground);
mRippleColor = null;
mBackground.mutate();
if (mBackground != null) {
@@ -148,46 +144,25 @@
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/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
index bbba19d..87df180 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 @@
/** 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 8f1ac81..422aa4d 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 @@
/** 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 @@
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();
+ }
- ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl",
+ @Override
+ public void onBaseStateChanged(int state) {
+ Assert.isMainThread();
+ mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
+
+ if (useBaseState()) {
+ sendUpdatePosture();
+ }
+ }
+
+ private void sendUpdatePosture() {
+ ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl",
l -> {
- l.onPostureChanged(mCurrentDevicePosture);
+ l.onPostureChanged(getDevicePosture());
return Unit.INSTANCE;
});
+ }
});
}
@@ -100,6 +119,14 @@
@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/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f4122d5..ea20d29 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 @@
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/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 0004f52..910097e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -21,6 +21,7 @@
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 @@
)
@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/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
index a9f8ea0..81d02b8 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 @@
`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 3808c7e..313ccb8 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 @@
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 fb5dd21..99993f2 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.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 @@
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 @@
testScope.runTest {
bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext))
- assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean())
}
}
@@ -136,7 +132,6 @@
backgroundExecutor.execute {
bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext))
- assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean())
}
}
@@ -147,7 +142,6 @@
testScope.runTest {
bluetoothTileDialogViewModel.showDialog(context, null)
- assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
verify(deviceItemInteractor).deviceItemUpdate
}
}
@@ -157,7 +151,6 @@
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 4c173cc..e236f4a 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 @@
@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/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
new file mode 100644
index 0000000..cf076c5
--- /dev/null
+++ b/packages/SystemUI/tests/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/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
new file mode 100644
index 0000000..eebb145
--- /dev/null
+++ b/packages/SystemUI/tests/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/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c3294ff..c953743 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -61,7 +61,6 @@
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 +272,9 @@
}
@Test
+ fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(SceneKey.Lockscreen) }
+
+ @Test
fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
testScope.runTest {
emulateUserDrivenTransition(SceneKey.Bouncer)
@@ -336,7 +338,7 @@
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.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index c4ec56c..adc1c61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -145,6 +145,18 @@
}
@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 })
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 5969bd8..0173c32 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.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 @@
.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 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 @@
@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/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
new file mode 100644
index 0000000..ce47170
--- /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/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 0000000..29702eb
--- /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 0000000..d2351dc
--- /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 13910fd..ccba072 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 @@
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 @@
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 0000000..ccf0391
--- /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/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b5e8c84..4b10111 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5580,6 +5580,8 @@
@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 d31b1ef..e3797c9 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 void logMagnificationTripleTap(boolean enabled) {
AccessibilityStatsLogUtils.logMagnificationTripleTap(enabled);
}
+
+ @Override
+ public void logMagnificationTwoFingerTripleTap(boolean enabled) {
+ AccessibilityStatsLogUtils.logMagnificationTwoFingerTripleTap(enabled);
+ }
};
}
@@ -419,6 +424,7 @@
/** An interface that allows testing magnification log events. */
interface MagnificationLogger {
void logMagnificationTripleTap(boolean enabled);
+ void logMagnificationTwoFingerTripleTap(boolean enabled);
}
interface State {
@@ -987,12 +993,14 @@
mDisplayId, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
+ } 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 (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
- onTripleTap(event);
-
} else if (
// Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
isFingerDown()
@@ -1026,6 +1034,11 @@
mCompletedTapCount++;
mIsTwoFingerCountReached = false;
}
+
+ if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) {
+ final boolean enabled = !isActivated();
+ mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+ }
return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount;
}
@@ -1037,6 +1050,29 @@
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 @@
// 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/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 3583a78..a159a5e 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 4987fbc..45d7314 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -939,7 +939,7 @@
if (mVirtualCameraController == null) {
throw new UnsupportedOperationException("Virtual camera controller is not available");
}
- mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig));
+ mVirtualCameraController.registerCamera(cameraConfig);
}
@Override // Binder call
@@ -951,7 +951,19 @@
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 d194bb2..9b78ed4 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 @@
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 @@
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 @@
}
@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 06be3f3..5665ad5 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
@@ -67,14 +67,7 @@
* @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)) {
@@ -110,6 +103,17 @@
}
}
+ /** Return the id of the virtual camera with the given config. */
+ public int getCameraId(@NonNull VirtualCameraConfig cameraConfig) {
+ connectVirtualCameraServiceIfNeeded();
+
+ try {
+ return mVirtualCameraService.getCameraId(cameraConfig.getCallback().asBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public void binderDied() {
Slog.d(TAG, "Virtual camera service died.");
@@ -152,6 +156,17 @@
}
}
+ private void connectVirtualCameraServiceIfNeeded() {
+ // 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.");
+ }
+ }
+
private void connectVirtualCameraService() {
final long callingId = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index fc56511..23c008e 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.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 @@
@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 77b6d583..eb3ec24 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1466,8 +1466,11 @@
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 7fe0682..c258370 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
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f6835fe..39b8643 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -215,7 +215,7 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 c718d39..9eb35fd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2096,14 +2096,48 @@
/**
* 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 003046a..3135650 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -98,10 +98,16 @@
// 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 WAITED_UNTIL_PRE_WATCHDOG = 2;
private static final int OVERDUE = 3;
// Track watchdog timeout history and break the crash loop if there is.
@@ -310,10 +316,10 @@
return COMPLETED;
} else {
long latency = SystemClock.uptimeMillis() - mStartTimeMillis;
- if (latency < mWaitMaxMillis / 2) {
+ if (latency < mWaitMaxMillis / PRE_WATCHDOG_TIMEOUT_RATIO) {
return WAITING;
} else if (latency < mWaitMaxMillis) {
- return WAITED_HALF;
+ return WAITED_UNTIL_PRE_WATCHDOG;
}
}
return OVERDUE;
@@ -368,8 +374,9 @@
public void pauseForLocked(int pauseMillis, String reason) {
mPauseEndTimeMillis = SystemClock.uptimeMillis() + 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 +386,9 @@
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);
@@ -797,11 +805,11 @@
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 +856,16 @@
} 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 +883,12 @@
// 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 +937,8 @@
}
}
- 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 +950,7 @@
}
final String dropboxTag;
- if (halfWatchdog) {
+ if (preWatchdog) {
dropboxTag = "pre_watchdog";
CriticalEventLog.getInstance().logHalfWatchdog(subject);
FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED);
@@ -971,7 +980,7 @@
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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e656030..1928780 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -3013,7 +3013,7 @@
mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
- mCpuScalingPolicies, null);
+ mCpuScalingPolicies, new PowerStatsUidResolver());
checkinStats.readSummaryFromParcel(in);
in.recycle();
checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
@@ -3055,7 +3055,7 @@
mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
- mCpuScalingPolicies, null);
+ mCpuScalingPolicies, new PowerStatsUidResolver());
checkinStats.readSummaryFromParcel(in);
in.recycle();
checkinStats.dumpCheckin(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 192fd6f..69e3aaf 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -152,11 +152,13 @@
"mainline_sdk",
"media_audio",
"media_drm",
+ "media_reliability",
"media_tv",
"media_solutions",
"nfc",
"pdf_viewer",
"pixel_audio_android",
+ "pixel_bluetooth",
"pixel_system_sw_touch",
"pixel_watch",
"platform_security",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d4b72c1..3f27ca0 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 @@
}
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 @@
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
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 be78ea2..cecde55 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 @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 debf828..d848f4b 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 @@
// Display independent, mode dependent values
float[] brightnessLevelsNits;
+ float[] brightnessLevels = null;
float[] luxLevels;
if (isForIdleMode) {
brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
@@ -130,11 +131,21 @@
} 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 @@
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 @@
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 @@
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 70d4ad2..c26118e 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.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 @@
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 @@
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 @@
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 2fdf90d..3b05b47 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 @@
}
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 b99de5c..d97127c 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.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.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 @@
* <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 @@
* <majorVersion>2</majorVersion>
* <minorVersion>0</minorVersion>
* </usiVersion>
+ * <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
* </displayConfiguration>
* }
* </pre>
@@ -629,7 +634,6 @@
// 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 @@
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 @@
@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 @@
* @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 @@
* 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 @@
// 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 @@
}
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 @@
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 @@
}
/**
+ * @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 @@
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 @@
+ mAutoBrightnessDarkeningLightDebounceIdle
+ ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+ ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels)
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "\n"
@@ -1867,36 +1901,39 @@
+ ", 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 @@
loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
loadUsiVersion(config);
mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
+ loadBrightnessCapForWearBedtimeMode(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -1961,6 +1999,7 @@
loadAutoBrightnessConfigsFromConfigXml();
loadAutoBrightnessAvailableFromConfigXml();
loadRefreshRateSetting(null);
+ loadBrightnessCapForWearBedtimeModeFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -2599,8 +2638,23 @@
* 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 @@
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 @@
: 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 6a7c17d..2ab15e6 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 @@
// 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 @@
private void registerOverlayDisplayAdapterLocked() {
registerDisplayAdapterLocked(new OverlayDisplayAdapter(
- mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler));
+ mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler, mFlags));
}
private void registerWifiDisplayAdapterLocked() {
@@ -1880,7 +1880,7 @@
|| SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
mWifiDisplayAdapter = new WifiDisplayAdapter(
mSyncRoot, mContext, mHandler, mDisplayDeviceRepo,
- mPersistentDataStore);
+ mPersistentDataStore, mFlags);
registerDisplayAdapterLocked(mWifiDisplayAdapter);
}
}
@@ -3288,8 +3288,10 @@
@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 ff9a1ab..22898a6 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 @@
private final boolean mIsBootDisplayModeSupported;
- private final DisplayManagerFlags mFlags;
-
private final DisplayNotificationManager mDisplayNotificationManager;
private Context mOverlayContext;
@@ -103,12 +101,11 @@
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 @@
// 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 @@
+ ", 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 @@
}
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 2ce7690..22ff2d0 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 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 @@
// 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 edbd424..ec5ad7d 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.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 @@
// 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 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 7660cf8..aa98cd8 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 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 @@
// 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 dfcda40..8a884b6 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 @@
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 @@
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 b574919..765608e 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 @@
}
};
- 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 @@
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 @@
clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
data));
}
+ if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
+ clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
+ clamperChangeListener, data));
+ }
return clampers;
}
@@ -257,7 +262,8 @@
* 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 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 339b589..790322d 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 @@
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 @@
@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 8ae962b..944a8a6 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 @@
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 @@
@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 @@
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 0000000..7e853bf
--- /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 5cfbf26..c71f0cf2 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 @@
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 @@
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 @@
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 d95bdae..9dfa1ee 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 @@
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 8eb03ec..8fa838d 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.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 @@
}
@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 @@
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 @@
}
}
- 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 @@
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 @@
}
}
- @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 @@
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
- mSettingsObserver.updateRefreshRateSettingLocked(displayId);
}
@Nullable
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 6f9b7d6..27b01a5 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.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 @@
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 @@
}
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/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 3b3d79e..07e0ddf 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 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 0a81b2b..5494bd9 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 @@
}
@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 d0fdfa9..9384c13 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 @@
// 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/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index e3f3638..5d90851 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.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.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 @@
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 @@
// 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/utils/UserSettingDeviceConfigMediator.java b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java
new file mode 100644
index 0000000..e542349
--- /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 b12da61..e9c4096 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 @@
}
@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 26c3fba..60dc4ff 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -421,6 +421,13 @@
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 89cb4c8..29782d9 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.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 @@
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 a9c3dc4..1772ef9 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 @@
WebViewProviderInfo[] getValidWebViewPackages();
+ WebViewProviderInfo getDefaultWebViewPackage();
+
PackageInfo getCurrentWebViewPackage();
boolean isMultiProcessEnabled();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6302e0..bbaa691 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 @@
// An activity has changed order/visibility or the task is occluded by a transient
// activity, so this isn't just deliver-to-top
&& mMovedToTopActivity == null
+ && !transitionController.hasOrderChanges()
&& !transitionController.isTransientHide(startedActivityRootTask)) {
// We just delivered to top, so there isn't an actual transition here.
if (!forceTransientTransition) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 39e900a..6e7cf12 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -63,6 +63,7 @@
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;
@@ -267,20 +268,25 @@
mIntent = intent;
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
if (originatingPendingIntent == null) {
- // grant creator BAL privileges unless explicitly opted out
+ // grant BAL privileges unless explicitly opted out
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(
callingUid, callingPackage, checkedOptions);
+ mBalAllowedByPiSender =
+ PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
+ checkedOptions, realCallingUid, mRealCallingPackage);
}
- mBalAllowedByPiSender =
- PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
- checkedOptions, realCallingUid, mRealCallingPackage);
mAppSwitchState = mService.getBalAppSwitchesState();
mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
mIsCallingUidPersistentSystemProcess =
@@ -579,11 +585,12 @@
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 +599,12 @@
&& 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 +724,18 @@
// 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 +815,29 @@
"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 2fabb0e..7ce9de4 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 @@
/**
* 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 @@
/**
* 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 0000000..e91857f
--- /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 bb59936..50376fe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1648,7 +1648,7 @@
? new Transition.ReadyCondition("displayConfig", this) : null;
if (displayConfig != null) {
mTransitionController.waitFor(displayConfig);
- } else if (mTransitionController.isShellTransitionsEnabled()) {
+ } else if (mTransitionController.isShellTransitionsEnabled() && mLastHasContent) {
Slog.e(TAG, "Display reconfigured outside of a transition: " + this);
}
final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
index 2549bbf..b5d94a2 100644
--- a/services/core/java/com/android/server/wm/SmoothDimmer.java
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -17,265 +17,203 @@
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;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ DimState mDimState;
+ 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(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 */);
- }
-
- // 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(WindowContainer)},
- * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
- * {@link Change#setRequestedAppearance(float, int)}
- */
- 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);
- }
- 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);
- }
- }
- 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;
- });
- }
-
- private boolean isAnimating() {
- return mAlphaAnimationSpec != null;
- }
-
- private boolean aspectChanged() {
- return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
- || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
- }
-
- private boolean dimmingContainerChanged() {
- return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
- }
-
- 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
- );
- }
- }
-
- protected SmoothDimmer(WindowContainer host) {
- this(host, new AnimationAdapterFactory());
- }
-
- @VisibleForTesting
- SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
- super(host);
- mAnimationAdapterFactory = animationFactory;
- }
-
- private DimState obtainDimState(WindowContainer container) {
- if (mDimState == null) {
+ DimState() {
+ mHostContainer = mHost;
+ mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
try {
- final SurfaceControl ctl = makeDimLayer();
- mDimState = new DimState(ctl);
+ mDimSurface = makeDimLayer();
} catch (Surface.OutOfResourcesException e) {
Log.w(TAG, "OutOfResourcesException creating dim surface");
}
}
- mLastRequestedDimContainer = container;
- return mDimState;
+ void ensureVisible(SurfaceControl.Transaction t) {
+ if (!mIsVisible) {
+ t.show(mDimSurface);
+ t.setAlpha(mDimSurface, 0f);
+ mIsVisible = true;
+ }
+ }
+
+ 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());
+ }
+
+ /**
+ * Set the parameters to prepare the dim to change its appearance
+ */
+ void prepareLookChange(float alpha, int blurRadius) {
+ mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
+ }
+
+ /**
+ * Prepare the dim for the exit animation
+ */
+ void exit(SurfaceControl.Transaction t) {
+ if (!mAnimateExit) {
+ remove(t);
+ } else {
+ mAnimationHelper.setExitParameters();
+ setReady(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");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SmoothDimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Whether anyone is currently requesting the dim
+ */
+ boolean isDimming() {
+ return mLastRequestedDimContainer != null;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("DimLayer.makeDimLayer")
+ .build();
+ }
}
- private SurfaceControl makeDimLayer() {
- return mHost.makeChildSurface(null)
- .setParent(mHost.getSurfaceControl())
- .setColorLayer()
- .setName("Dim Layer for - " + mHost.getName())
- .setCallsite("Dimmer.makeDimLayer")
- .build();
+ protected SmoothDimmer(WindowContainer host) {
+ this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
}
- @Override
- SurfaceControl getDimLayer() {
- return mDimState != null ? mDimState.mDimLayer : null;
+ @VisibleForTesting
+ SmoothDimmer(WindowContainer host,
+ DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
+ super(host);
+ mAnimationAdapterFactory = animationFactory;
}
@Override
void resetDimStates() {
+ if (mDimState != null) {
+ mDimState.mLastRequestedDimContainer = null;
+ }
+ }
+
+ @Override
+ protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+ d.prepareLookChange(alpha, blurRadius);
+ }
+
+ @Override
+ protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+ if (mDimState != null) {
+ mDimState.prepareReparent(container, relativeLayer);
+ }
+ }
+
+ @Override
+ boolean updateDims(SurfaceControl.Transaction t) {
if (mDimState == null) {
- return;
+ return false;
}
- if (!mDimState.mDontReset) {
- mDimState.mDimming = false;
+ 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;
}
}
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ mDimState = new DimState();
+ }
+ mDimState.mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ @Override
+ @VisibleForTesting
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimSurface : null;
+ }
+
@Override
Rect getDimBounds() {
return mDimState != null ? mDimState.mDimBounds : null;
@@ -287,127 +225,4 @@
mDimState.mAnimateExit = false;
}
}
-
- @Override
- protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
- final DimState d = obtainDimState(container);
- mDimState.setRequestedAppearance(alpha, blurRadius);
- d.mDimming = true;
- }
-
- @Override
- protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
- if (mDimState != null) {
- mDimState.setRequestedRelativeParent(container, relativeLayer);
- }
- }
-
- 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);
- }
- mDimState = null;
- return false;
- }
- 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 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);
- }
- }
-
- 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/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7375512..de802b9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6177,6 +6177,7 @@
// 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;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index caa57bb..e5604ec 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 @@
// 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 @@
}
/**
+ * 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 @@
*/
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 bacfda5..e648d64 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 @@
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/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index cca4261..c625b1e 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
+ <!-- Sets the list of display brightness points, each representing the desired screen brightness
+ in a certain lux environment.
- The N entries of this array define N + 1 control points as follows:
- (1-based arrays)
+ The first value of each point is the lux value and the second value is the brightness value.
- 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 first lux value must be 0.
- 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>
+ 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 f767291..8c8c123 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 @@
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 @@
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 @@
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 @@
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 @@
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 1988bb6..da44aac 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.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 @@
private final Set<ComponentName> mEnabledProviders;
+ private final RequestSessionDeathRecipient mDeathRecipient =
+ new RequestSessionDeathRecipient();
+
protected PendingIntent mPendingIntent;
@NonNull
@@ -141,11 +146,26 @@
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 @@
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 @@
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/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 97e5826..a2e80f0 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 @@
468.5f,
};
- private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
+ private static final int[] DISPLAY_LEVELS_INT = {
9,
30,
45,
@@ -118,6 +118,20 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
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 0bcbeb9..31d7e88 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.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 @@
@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 @@
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 @@
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 @@
assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+
+ assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35),
+ mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
}
@Test
@@ -716,6 +721,38 @@
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 @@
+ "<screenBrightnessRampDecreaseMaxIdleMillis>"
+ "5000"
+ "</screenBrightnessRampDecreaseMaxIdleMillis>\n";
-
}
+
private String getContent() {
return getContent(getValidLuxThrottling(), getValidProxSensor(),
/* includeIdleMode= */ true);
@@ -1089,16 +1126,18 @@
+ "<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 @@
+ "<majorVersion>2</majorVersion>\n"
+ "<minorVersion>0</minorVersion>\n"
+ "</usiVersion>\n"
+ + "<screenBrightnessCapForWearBedtimeMode>"
+ + "0.1"
+ + "</screenBrightnessCapForWearBedtimeMode>"
+ "</displayConfiguration>\n";
}
@@ -1372,7 +1414,7 @@
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 @@
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 @@
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 4fd8f26..dc6abf1 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 @@
@Mock
private SurfaceControl.Transaction mMockTransaction;
+ @Mock
+ private DisplayAdapter mMockDisplayAdapter;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -67,34 +70,39 @@
@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 @@
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 55f56e9..02e3ef4 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 @@
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 @@
@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 @@
@Override
public void destroyDisplay(IBinder displayToken) {
}
- });
+ }, flags);
}
@Override
@@ -329,6 +331,7 @@
@Mock SensorManager mSensorManager;
@Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
@Mock PackageManagerInternal mMockPackageManagerInternal;
+ @Mock DisplayAdapter mMockDisplayAdapter;
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@Mock DisplayManagerFlags mMockFlags;
@@ -788,7 +791,8 @@
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 @@
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 8270845..f36854b 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 @@
// 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 43d2b8f..28ec896 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 @@
@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 @@
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 9f91916..5676a38 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.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 @@
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 @@
@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 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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 b7cbac5..5c50acb 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 @@
@Test
public void testFindHighestRefreshRateForDefaultDisplay() {
- when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
- assertEquals(120,
- RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
- /* delta= */ 0);
- }
-
- @Test
- public void testFindHighestRefreshRate() {
- int displayId = 13;
- when(mDisplayManagerMock.getDisplay(displayId)).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);
+ when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+ assertEquals(120,
+ 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 73e7ba0..c01b15c 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 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 @@
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 ff2b1f4..9aa6136 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 @@
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 0000000..3458b08
--- /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 d085923..60a0c03 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 @@
};
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 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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
@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 be29163..1a3a6a3 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.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 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 @@
@Mock
private ConnectivityManager mConnectivityManager;
@Mock
+ private ContentResolver mContentResolver;
+ @Mock
private IActivityManager mIActivityManager;
@Mock
private LocationManager mLocationManager;
@@ -332,7 +336,7 @@
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 @@
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 @@
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/location/gnss/GnssLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
new file mode 100644
index 0000000..c5e6824
--- /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 931b38d..f8fe97e 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_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 @@
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/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index fd2cf6d..3b39160 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 @@
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 @@
twoFingerTapAndHold();
assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
+ verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+ verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
}
@Test
@@ -576,6 +583,8 @@
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/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index b5ba322..9213601a 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 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 @@
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/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 6ae2658..cd29c80 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.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 @@
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 @@
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 0000000..377e4c3
--- /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 d000605..32082e3 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.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 @@
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/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f2a1fe8..c3074bb 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/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 9f58491..9efbe35 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.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 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 @@
}
}
- 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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 8119fd4..3b9ed26 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 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.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.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 @@
private SurfaceControlViewHost mScvh1;
private SurfaceControlViewHost mScvh2;
+ private SurfaceView mSurfaceView;
+
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -96,15 +100,17 @@
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 @@
}
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 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 3cb4a1d..e65a9fe 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 @@
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 @@
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 b1def8d..51f0404 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 @@
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/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index dade3b9..71447e7 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 @@
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 0000000..c12dcdd
--- /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 bfb159f..dce4818 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.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.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 @@
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 @@
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 8138747..d865345 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 @@
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.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 @@
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 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 4c56f33..0e1e0c8 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.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.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 @@
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 @@
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 @@
}
}
+ 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 @@
@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 @@
}
@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 9b67ab6..3bc7752 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 @@
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();
}