Merge "Make APIs in the base CellInfo class public."
diff --git a/apct-tests/perftests/autofill/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml
index 9c8abc3..1e3532b 100644
--- a/apct-tests/perftests/autofill/AndroidManifest.xml
+++ b/apct-tests/perftests/autofill/AndroidManifest.xml
@@ -18,7 +18,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.perftests.utils.StubActivity">
+ <activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
diff --git a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
index 3aa6749..236f548 100644
--- a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
@@ -38,7 +38,8 @@
private static final String PERMISSION_NAME_DOESNT_EXIST =
"com.android.perftests.core.TestBadPermission";
private static final ComponentName TEST_ACTIVITY =
- new ComponentName("com.android.perftests.core", "android.perftests.utils.StubActivity");
+ new ComponentName("com.android.perftests.core",
+ "android.perftests.utils.PerfTestActivity");
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 3cfb080..c036c77 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -35,8 +35,6 @@
void onBootPhase(int phase);
- boolean isParoledOrCharging();
-
void postCheckIdleStates(int userId);
/**
@@ -59,13 +57,15 @@
int getAppId(String packageName);
- boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+ /**
+ * @see #isAppIdleFiltered(String, int, int, long)
+ */
+ boolean isAppIdleFiltered(String packageName, int userId, long elapsedRealtime,
boolean shouldObfuscateInstantApps);
/**
* Checks if an app has been idle for a while and filters out apps that are excluded.
* It returns false if the current system state allows all apps to be considered active.
- * This happens if the device is plugged in or temporarily allowed to make exceptions.
* Called by interface impls.
*/
boolean isAppIdleFiltered(String packageName, int appId, int userId,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 14d5a68..c3ffad6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -266,11 +266,6 @@
boolean mReportedActive;
/**
- * Are we currently in device-wide standby parole?
- */
- volatile boolean mInParole;
-
- /**
* A mapping of which uids are currently in the foreground to their effective priority.
*/
final SparseIntArray mUidPriorityOverride = new SparseIntArray();
@@ -2361,14 +2356,6 @@
}
@Override
- public void onParoleStateChanged(boolean isParoleOn) {
- if (DEBUG_STANDBY) {
- Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- mInParole = isParoleOn;
- }
-
- @Override
public void onUserInteractionStarted(String packageName, int userId) {
final int uid = mLocalPM.getPackageUid(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
@@ -3031,10 +3018,6 @@
}
pw.println();
- pw.print(" In parole?: ");
- pw.print(mInParole);
- pw.println();
-
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
pw.print(" ");
mJobRestrictions.get(i).dumpConstants(pw);
@@ -3222,7 +3205,6 @@
}
proto.end(settingsToken);
- proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
mJobRestrictions.get(i).dumpConstants(proto);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 400d902..14dce84 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -414,8 +414,6 @@
private final Handler mHandler;
private final QcConstants mQcConstants;
- private volatile boolean mInParole;
-
/** How much time each app will have to run jobs within their standby bucket window. */
private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -711,7 +709,6 @@
public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
// If quota is currently "free", then the job can run for the full amount of time.
if (mChargeTracker.isCharging()
- || mInParole
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())) {
return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
@@ -737,8 +734,8 @@
final int standbyBucket) {
if (standbyBucket == NEVER_INDEX) return false;
- // Quota constraint is not enforced while charging or when parole is on.
- if (mChargeTracker.isCharging() || mInParole) {
+ // Quota constraint is not enforced while charging.
+ if (mChargeTracker.isCharging()) {
return true;
}
@@ -1780,20 +1777,6 @@
}
});
}
-
- @Override
- public void onParoleStateChanged(final boolean isParoleOn) {
- mInParole = isParoleOn;
- if (DEBUG) {
- Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- // Update job bookkeeping out of band.
- BackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- maybeUpdateAllConstraintsLocked();
- }
- });
- }
}
private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
@@ -2515,7 +2498,6 @@
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
pw.println("Is charging: " + mChargeTracker.isCharging());
- pw.println("In parole: " + mInParole);
pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
pw.println();
@@ -2639,7 +2621,6 @@
final long mToken = proto.start(StateControllerProto.QUOTA);
proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
- proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
sElapsedRealtimeClock.millis());
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 7c472a9..ecc0459 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -45,7 +45,6 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
-import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import android.annotation.UserIdInt;
@@ -70,10 +69,8 @@
import android.hardware.display.DisplayManager;
import android.net.ConnectivityManager;
import android.net.Network;
-import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
-import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Environment;
import android.os.Handler;
@@ -192,21 +189,14 @@
static final int MSG_INFORM_LISTENERS = 3;
static final int MSG_FORCE_IDLE_STATE = 4;
static final int MSG_CHECK_IDLE_STATES = 5;
- static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
- static final int MSG_PAROLE_END_TIMEOUT = 7;
static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
- static final int MSG_PAROLE_STATE_CHANGED = 9;
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
/** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */
static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11;
static final int MSG_REPORT_SYNC_SCHEDULED = 12;
static final int MSG_REPORT_EXEMPTED_SYNC_START = 13;
- static final int MSG_UPDATE_STABLE_CHARGING= 14;
long mCheckIdleIntervalMillis;
- long mAppIdleParoleIntervalMillis;
- long mAppIdleParoleWindowMillis;
- long mAppIdleParoleDurationMillis;
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
/** Minimum time a strong usage event should keep the bucket elevated. */
@@ -244,20 +234,12 @@
* start is the first usage of the app
*/
long mInitialForegroundServiceStartTimeoutMillis;
- /** The length of time phone must be charging before considered stable enough to run jobs */
- long mStableChargingThresholdMillis;
private volatile boolean mAppIdleEnabled;
- boolean mAppIdleTempParoled;
- boolean mCharging;
- boolean mChargingStable;
- private long mLastAppIdleParoledTime;
private boolean mSystemServicesReady = false;
// There was a system update, defaults need to be initialized after services are ready
private boolean mPendingInitializeDefaults;
- private final DeviceStateReceiver mDeviceStateReceiver;
-
private volatile boolean mPendingOneTimeCheckIdleStates;
private final AppStandbyHandler mHandler;
@@ -267,7 +249,6 @@
private AppWidgetManager mAppWidgetManager;
private ConnectivityManager mConnectivityManager;
- private PowerManager mPowerManager;
private PackageManager mPackageManager;
Injector mInjector;
@@ -329,12 +310,6 @@
mContext = mInjector.getContext();
mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
- mDeviceStateReceiver = new DeviceStateReceiver();
-
- IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
- deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
- deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
- mContext.registerReceiver(mDeviceStateReceiver, deviceStates);
synchronized (mAppIdleLock) {
mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
@@ -353,15 +328,7 @@
@VisibleForTesting
void setAppIdleEnabled(boolean enabled) {
- synchronized (mAppIdleLock) {
- if (mAppIdleEnabled != enabled) {
- final boolean oldParoleState = isParoledOrCharging();
- mAppIdleEnabled = enabled;
- if (isParoledOrCharging() != oldParoleState) {
- postParoleStateChanged();
- }
- }
- }
+ mAppIdleEnabled = enabled;
}
@Override
@@ -381,7 +348,6 @@
mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
- mPowerManager = mContext.getSystemService(PowerManager.class);
mInjector.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mAppIdleLock) {
@@ -402,8 +368,6 @@
if (mPendingOneTimeCheckIdleStates) {
postOneTimeCheckIdleStates();
}
- } else if (phase == PHASE_BOOT_COMPLETED) {
- setChargingState(mInjector.isCharging());
}
}
@@ -504,93 +468,6 @@
}
}
- @VisibleForTesting
- void setChargingState(boolean charging) {
- synchronized (mAppIdleLock) {
- if (mCharging != charging) {
- mCharging = charging;
- if (DEBUG) Slog.d(TAG, "Setting mCharging to " + charging);
- if (charging) {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling MSG_UPDATE_STABLE_CHARGING delay = "
- + mStableChargingThresholdMillis);
- }
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STABLE_CHARGING,
- mStableChargingThresholdMillis);
- } else {
- mHandler.removeMessages(MSG_UPDATE_STABLE_CHARGING);
- updateChargingStableState();
- }
- }
- }
- }
-
- private void updateChargingStableState() {
- synchronized (mAppIdleLock) {
- if (mChargingStable != mCharging) {
- if (DEBUG) Slog.d(TAG, "Setting mChargingStable to " + mCharging);
- mChargingStable = mCharging;
- postParoleStateChanged();
- }
- }
- }
-
- private void setAppIdleParoled(boolean paroled) {
- synchronized (mAppIdleLock) {
- final long now = mInjector.currentTimeMillis();
- if (mAppIdleTempParoled != paroled) {
- mAppIdleTempParoled = paroled;
- if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
- if (paroled) {
- postParoleEndTimeout();
- } else {
- mLastAppIdleParoledTime = now;
- postNextParoleTimeout(now, false);
- }
- postParoleStateChanged();
- }
- }
- }
-
- @Override
- public boolean isParoledOrCharging() {
- if (!mAppIdleEnabled) return true;
- synchronized (mAppIdleLock) {
- // Only consider stable charging when determining charge state.
- return mAppIdleTempParoled || mChargingStable;
- }
- }
-
- private void postNextParoleTimeout(long now, boolean forced) {
- if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
- mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
- // Compute when the next parole needs to happen. We check more frequently than necessary
- // since the message handler delays are based on elapsedRealTime and not wallclock time.
- // The comparison is done in wallclock time.
- long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
- if (forced) {
- // Set next timeout for the end of the parole window
- // If parole is not set by the end of the window it will be forced
- timeLeft += mAppIdleParoleWindowMillis;
- }
- if (timeLeft < 0) {
- timeLeft = 0;
- }
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
- }
-
- private void postParoleEndTimeout() {
- if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
- mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
- mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
- }
-
- private void postParoleStateChanged() {
- if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
- mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
- mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
- }
-
@Override
public void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -787,48 +664,6 @@
return THRESHOLD_BUCKETS[bucketIndex];
}
- private void checkParoleTimeout() {
- boolean setParoled = false;
- boolean waitForNetwork = false;
- NetworkInfo activeNetwork = mConnectivityManager.getActiveNetworkInfo();
- boolean networkActive = activeNetwork != null &&
- activeNetwork.isConnected();
-
- synchronized (mAppIdleLock) {
- final long now = mInjector.currentTimeMillis();
- if (!mAppIdleTempParoled) {
- final long timeSinceLastParole = now - mLastAppIdleParoledTime;
- if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
- if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
- if (networkActive) {
- // If network is active set parole
- setParoled = true;
- } else {
- if (timeSinceLastParole
- > mAppIdleParoleIntervalMillis + mAppIdleParoleWindowMillis) {
- if (DEBUG) Slog.d(TAG, "Crossed end of parole window, force parole");
- setParoled = true;
- } else {
- if (DEBUG) Slog.d(TAG, "Network unavailable, delaying parole");
- waitForNetwork = true;
- postNextParoleTimeout(now, true);
- }
- }
- } else {
- if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
- postNextParoleTimeout(now, false);
- }
- }
- }
- if (waitForNetwork) {
- mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
- }
- if (setParoled) {
- // Set parole if network is available
- setAppIdleParoled(true);
- }
- }
-
private void notifyBatteryStats(String packageName, int userId, boolean idle) {
try {
final int uid = mPackageManager.getPackageUidAsUser(packageName,
@@ -844,30 +679,6 @@
}
}
- private void onDeviceIdleModeChanged() {
- final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
- if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
- boolean paroled = false;
- synchronized (mAppIdleLock) {
- final long timeSinceLastParole =
- mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
- if (!deviceIdle
- && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
- if (DEBUG) {
- Slog.i(TAG,
- "Bringing idle apps out of inactive state due to deviceIdleMode=false");
- }
- paroled = true;
- } else if (deviceIdle) {
- if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
- paroled = false;
- } else {
- return;
- }
- }
- setAppIdleParoled(paroled);
- }
-
@Override
public void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
if (!mAppIdleEnabled) return;
@@ -1038,11 +849,8 @@
}
@Override
- public boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+ public boolean isAppIdleFiltered(String packageName, int userId, long elapsedRealtime,
boolean shouldObfuscateInstantApps) {
- if (isParoledOrCharging()) {
- return false;
- }
if (shouldObfuscateInstantApps &&
mInjector.isPackageEphemeral(userId, packageName)) {
return false;
@@ -1388,15 +1196,6 @@
}
}
- private void informParoleStateChanged() {
- final boolean paroled = isParoledOrCharging();
- synchronized (mPackageAccessListeners) {
- for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
- listener.onParoleStateChanged(paroled);
- }
- }
- }
-
@Override
public void flushToDisk(int userId) {
synchronized (mAppIdleLock) {
@@ -1517,18 +1316,6 @@
TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
pw.println();
- pw.print(" mAppIdleParoleIntervalMillis=");
- TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleParoleWindowMillis=");
- TimeUtils.formatDuration(mAppIdleParoleWindowMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleParoleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
- pw.println();
-
pw.print(" mStrongUsageTimeoutMillis=");
TimeUtils.formatDuration(mStrongUsageTimeoutMillis, pw);
pw.println();
@@ -1566,22 +1353,11 @@
TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
pw.println();
- pw.print(" mStableChargingThresholdMillis=");
- TimeUtils.formatDuration(mStableChargingThresholdMillis, pw);
- pw.println();
-
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
- pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
- pw.print(" mCharging="); pw.print(mCharging);
- pw.print(" mChargingStable="); pw.print(mChargingStable);
- pw.print(" mLastAppIdleParoledTime=");
- TimeUtils.formatDuration(now - mLastAppIdleParoledTime, pw);
pw.println();
pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
- pw.print("mStableChargingThresholdMillis=");
- TimeUtils.formatDuration(mStableChargingThresholdMillis, pw);
pw.println();
}
@@ -1655,10 +1431,6 @@
return buildFlag && runtimeFlag;
}
- boolean isCharging() {
- return mContext.getSystemService(BatteryManager.class).isCharging();
- }
-
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
}
@@ -1748,15 +1520,6 @@
checkIdleStates(UserHandle.USER_ALL);
break;
- case MSG_CHECK_PAROLE_TIMEOUT:
- checkParoleTimeout();
- break;
-
- case MSG_PAROLE_END_TIMEOUT:
- if (DEBUG) Slog.d(TAG, "Ending parole");
- setAppIdleParoled(false);
- break;
-
case MSG_REPORT_CONTENT_PROVIDER_USAGE:
SomeArgs args = (SomeArgs) msg.obj;
reportContentProviderUsage((String) args.arg1, // authority name
@@ -1765,11 +1528,6 @@
args.recycle();
break;
- case MSG_PAROLE_STATE_CHANGED:
- if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
- + ", Charging state:" + mChargingStable);
- informParoleStateChanged();
- break;
case MSG_CHECK_PACKAGE_IDLE_STATE:
checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2,
mInjector.elapsedRealtime());
@@ -1788,10 +1546,6 @@
reportExemptedSyncStart((String) msg.obj, msg.arg1);
break;
- case MSG_UPDATE_STABLE_CHARGING:
- updateChargingStableState();
- break;
-
default:
super.handleMessage(msg);
break;
@@ -1800,23 +1554,6 @@
}
};
- private class DeviceStateReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case BatteryManager.ACTION_CHARGING:
- setChargingState(true);
- break;
- case BatteryManager.ACTION_DISCHARGING:
- setChargingState(false);
- break;
- case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
- onDeviceIdleModeChanged();
- break;
- }
- }
- }
-
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
private final ConnectivityManager.NetworkCallback mNetworkCallback
@@ -1824,7 +1561,6 @@
@Override
public void onAvailable(Network network) {
mConnectivityManager.unregisterNetworkCallback(this);
- checkParoleTimeout();
}
};
@@ -1851,9 +1587,6 @@
* Observe settings changes for {@link Global#APP_IDLE_CONSTANTS}.
*/
private class SettingsObserver extends ContentObserver {
- private static final String KEY_PAROLE_INTERVAL = "parole_interval";
- private static final String KEY_PAROLE_WINDOW = "parole_window";
- private static final String KEY_PAROLE_DURATION = "parole_duration";
private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
private static final String KEY_STRONG_USAGE_HOLD_DURATION = "strong_usage_duration";
@@ -1875,7 +1608,6 @@
"system_interaction_duration";
private static final String KEY_INITIAL_FOREGROUND_SERVICE_START_HOLD_DURATION =
"initial_foreground_service_start_duration";
- private static final String KEY_STABLE_CHARGING_THRESHOLD = "stable_charging_threshold";
public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR;
public static final long DEFAULT_NOTIFICATION_TIMEOUT = 12 * ONE_HOUR;
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR;
@@ -1885,7 +1617,6 @@
public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_DOZE_TIMEOUT = 4 * ONE_HOUR;
public static final long DEFAULT_EXEMPTED_SYNC_START_TIMEOUT = 10 * ONE_MINUTE;
public static final long DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT = 10 * ONE_MINUTE;
- public static final long DEFAULT_STABLE_CHARGING_THRESHOLD = 10 * ONE_MINUTE;
public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT = 30 * ONE_MINUTE;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -1932,17 +1663,6 @@
synchronized (mAppIdleLock) {
- // Default: 24 hours between paroles
- mAppIdleParoleIntervalMillis = mParser.getDurationMillis(KEY_PAROLE_INTERVAL,
- COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
-
- // Default: 2 hours to wait on network
- mAppIdleParoleWindowMillis = mParser.getDurationMillis(KEY_PAROLE_WINDOW,
- COMPRESS_TIME ? ONE_MINUTE * 2 : 2 * 60 * ONE_MINUTE);
-
- mAppIdleParoleDurationMillis = mParser.getDurationMillis(KEY_PAROLE_DURATION,
- COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
-
String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
SCREEN_TIME_THRESHOLDS);
@@ -1997,10 +1717,6 @@
KEY_INITIAL_FOREGROUND_SERVICE_START_HOLD_DURATION,
COMPRESS_TIME ? ONE_MINUTE :
DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT);
-
- mStableChargingThresholdMillis = mParser.getDurationMillis(
- KEY_STABLE_CHARGING_THRESHOLD,
- COMPRESS_TIME ? ONE_MINUTE : DEFAULT_STABLE_CHARGING_THRESHOLD);
}
// Check if app_idle_enabled has changed. Do this after getting the rest of the settings
diff --git a/api/current.txt b/api/current.txt
index d74a41b..6a7b53f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2946,6 +2946,7 @@
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
+ field public static final int FLAG_HANDLE_SHORTCUT = 2048; // 0x800
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
@@ -4481,7 +4482,7 @@
method @IntRange(from=0) public int getNotingUid();
method @NonNull public String getOp();
method @IntRange(from=0) public long getTime();
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
}
@@ -13772,7 +13773,9 @@
public enum Bitmap.CompressFormat {
enum_constant public static final android.graphics.Bitmap.CompressFormat JPEG;
enum_constant public static final android.graphics.Bitmap.CompressFormat PNG;
- enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP;
+ enum_constant @Deprecated public static final android.graphics.Bitmap.CompressFormat WEBP;
+ enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP_LOSSLESS;
+ enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP_LOSSY;
}
public enum Bitmap.Config {
@@ -25419,22 +25422,22 @@
public class MediaMetadataRetriever implements java.lang.AutoCloseable {
ctor public MediaMetadataRetriever();
method public void close();
- method public String extractMetadata(int);
- method public byte[] getEmbeddedPicture();
- method public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
- method public android.graphics.Bitmap getFrameAtIndex(int);
- method public android.graphics.Bitmap getFrameAtTime(long, int);
- method public android.graphics.Bitmap getFrameAtTime(long, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
- method public android.graphics.Bitmap getFrameAtTime(long);
- method public android.graphics.Bitmap getFrameAtTime();
+ method @Nullable public String extractMetadata(int);
+ method @Nullable public byte[] getEmbeddedPicture();
+ method @Nullable public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
+ method @Nullable public android.graphics.Bitmap getFrameAtIndex(int);
+ method @Nullable public android.graphics.Bitmap getFrameAtTime(long, int);
+ method @Nullable public android.graphics.Bitmap getFrameAtTime(long, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
+ method @Nullable public android.graphics.Bitmap getFrameAtTime(long);
+ method @Nullable public android.graphics.Bitmap getFrameAtTime();
method @NonNull public java.util.List<android.graphics.Bitmap> getFramesAtIndex(int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
method @NonNull public java.util.List<android.graphics.Bitmap> getFramesAtIndex(int, int);
- method public android.graphics.Bitmap getImageAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
- method public android.graphics.Bitmap getImageAtIndex(int);
- method public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams);
- method public android.graphics.Bitmap getPrimaryImage();
- method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
- method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
+ method @Nullable public android.graphics.Bitmap getImageAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
+ method @Nullable public android.graphics.Bitmap getImageAtIndex(int);
+ method @Nullable public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams);
+ method @Nullable public android.graphics.Bitmap getPrimaryImage();
+ method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
+ method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
method public void release();
method public void setDataSource(String) throws java.lang.IllegalArgumentException;
method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -44256,6 +44259,7 @@
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
+ field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
diff --git a/api/system-current.txt b/api/system-current.txt
index 6e35200..117be3b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5691,14 +5691,6 @@
}
-package android.os.telephony {
-
- public class TelephonyRegistryManager {
- method public void notifyCarrierNetworkChange(boolean);
- }
-
-}
-
package android.permission {
public final class PermissionControllerManager {
@@ -8379,6 +8371,14 @@
field public static final int SRVCC_STATE_HANDOVER_STARTED = 0; // 0x0
}
+ public class TelephonyRegistryManager {
+ method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+ method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+ method public void notifyCarrierNetworkChange(boolean);
+ method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
+ method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
diff --git a/api/test-current.txt b/api/test-current.txt
index 2c7e44d..b9ab375 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2798,6 +2798,10 @@
method public void exitBackgroundAudioProcessing(boolean);
}
+ public static class Call.Details {
+ method public String getTelecomCallId();
+ }
+
public final class CallAudioState implements android.os.Parcelable {
ctor public CallAudioState(boolean, int, int, @Nullable android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.bluetooth.BluetoothDevice>);
}
diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp
index 92200f9..b59d88d 100755
--- a/cmds/statsd/src/socket/StatsSocketListener.cpp
+++ b/cmds/statsd/src/socket/StatsSocketListener.cpp
@@ -56,8 +56,7 @@
}
// + 1 to ensure null terminator if MAX_PAYLOAD buffer is received
- char buffer[sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time) + LOGGER_ENTRY_MAX_PAYLOAD +
- 1];
+ char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1];
struct iovec iov = {buffer, sizeof(buffer) - 1};
alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))];
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 4603f08..47fdcde 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -850,6 +850,7 @@
GestureResultCallbackInfo callbackInfo;
synchronized (mLock) {
callbackInfo = mGestureStatusCallbackInfos.get(sequence);
+ mGestureStatusCallbackInfos.remove(sequence);
}
final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index cf24b8e..e738b19 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+import android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.UnsupportedAppUsage;
@@ -322,6 +323,14 @@
*/
public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 0x00000400;
+ /**
+ * This flag indicates that the accessibility service will handle the shortcut action itself.
+ * A callback {@link AccessibilityButtonCallback#onClicked(AccessibilityButtonController)} is
+ * called when the user presses the accessibility shortcut. Otherwise, the service is enabled
+ * or disabled by the system instead.
+ */
+ public static final int FLAG_HANDLE_SHORTCUT = 0x00000800;
+
/** {@hide} */
public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
@@ -423,12 +432,13 @@
* @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
* @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
* @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
+ * @see #FLAG_HANDLE_SHORTCUT
*/
public int flags;
/**
* Whether or not the service has crashed and is awaiting restart. Only valid from {@link
- * android.view.accessibility.AccessibilityManager#getEnabledAccessibilityServiceList(int)},
+ * android.view.accessibility.AccessibilityManager#getInstalledAccessibilityServiceList()},
* because that is populated from the internal list of running services.
*
* @hide
@@ -1103,6 +1113,8 @@
return "FLAG_REQUEST_FINGERPRINT_GESTURES";
case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK:
return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK";
+ case FLAG_HANDLE_SHORTCUT:
+ return "FLAG_HANDLE_SHORTCUT";
default:
return null;
}
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index df6533a..241895c 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -66,14 +66,18 @@
- // Code below generated by codegen v1.0.0.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
//
- // CHECKSTYLE:OFF Generated code
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
/**
* Creates a new AsyncNotedAppOp.
@@ -83,7 +87,8 @@
* @param notingUid
* Uid that noted the op
* @param notingPackageName
- * Package that noted the op
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
* @param message
* Message associated with the noteOp. This message is set by the app noting the op
* @param time
@@ -127,7 +132,8 @@
}
/**
- * Package that noted the op
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
*/
@DataClass.Generated.Member
public @Nullable String getNotingPackageName() {
@@ -152,7 +158,7 @@
@Override
@DataClass.Generated.Member
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -187,7 +193,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -205,6 +211,41 @@
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ AsyncNotedAppOp(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int opCode = in.readInt();
+ int notingUid = in.readInt();
+ String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ String message = in.readString();
+ long time = in.readLong();
+
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mNotingUid = notingUid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mNotingUid,
+ "from", 0);
+ this.mNotingPackageName = notingPackageName;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mTime = time;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTime,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
= new Parcelable.Creator<AsyncNotedAppOp>() {
@@ -214,29 +255,14 @@
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
- public AsyncNotedAppOp createFromParcel(android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int opCode = in.readInt();
- int notingUid = in.readInt();
- String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
- String message = in.readString();
- long time = in.readLong();
- return new AsyncNotedAppOp(
- opCode,
- notingUid,
- notingPackageName,
- message,
- time);
+ public AsyncNotedAppOp createFromParcel(@NonNull android.os.Parcel in) {
+ return new AsyncNotedAppOp(in);
}
};
@DataClass.Generated(
- time = 1566503083973L,
- codegenVersion = "1.0.0",
+ time = 1571246617363L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
inputSignatures = "private final @android.annotation.IntRange(from=0L, to=91L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
@Deprecated
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 58f6741..6dca5d9 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1029,7 +1029,7 @@
* Returns AutomaticZenRules owned by the caller.
*
* <p>
- * Throws a SecurityException if policy access is granted to this package.
+ * Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*/
public Map<String, AutomaticZenRule> getAutomaticZenRules() {
@@ -1053,7 +1053,7 @@
* Returns the AutomaticZenRule with the given id, if it exists and the caller has access.
*
* <p>
- * Throws a SecurityException if policy access is granted to this package.
+ * Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
* <p>
@@ -1073,7 +1073,7 @@
* Creates the given zen rule.
*
* <p>
- * Throws a SecurityException if policy access is granted to this package.
+ * Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
* @param automaticZenRule the rule to create.
@@ -1092,7 +1092,7 @@
* Updates the given zen rule.
*
* <p>
- * Throws a SecurityException if policy access is granted to this package.
+ * Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
* <p>
@@ -1134,7 +1134,7 @@
* Deletes the automatic zen rule with the given id.
*
* <p>
- * Throws a SecurityException if policy access is granted to this package.
+ * Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
* <p>
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e81dc1c..8350fa1 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -152,7 +152,7 @@
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
import android.os.storage.StorageManager;
-import android.os.telephony.TelephonyRegistryManager;
+import android.telephony.TelephonyRegistryManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.print.IPrintManager;
@@ -611,7 +611,7 @@
new CachedServiceFetcher<TelephonyRegistryManager>() {
@Override
public TelephonyRegistryManager createService(ContextImpl ctx) {
- return new TelephonyRegistryManager();
+ return new TelephonyRegistryManager(ctx);
}});
registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c3c383c..8ea1ff5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2330,6 +2330,12 @@
"android.app.action.ADMIN_POLICY_COMPLIANCE";
/**
+ * Maximum supported password length. Kind-of arbitrary.
+ * @hide
+ */
+ public static final int MAX_PASSWORD_LENGTH = 16;
+
+ /**
* Return true if the given administrator component is currently active (enabled) in the system.
*
* @param admin The administrator component to check for.
@@ -3233,6 +3239,22 @@
}
/**
+ * Returns minimum PasswordMetrics that satisfies all admin policies.
+ *
+ * @hide
+ */
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.getPasswordMinimumMetrics(userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by an application that is administering the device to set the length of the password
* history. After setting this, the user will not be able to enter a new password that is the
* same as any password in the history. Note that the current password will remain until the
@@ -3415,8 +3437,7 @@
if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
return 0;
}
- // Kind-of arbitrary.
- return 16;
+ return MAX_PASSWORD_LENGTH;
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0da5b7a..7d2c54e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -72,6 +72,8 @@
void setPasswordMinimumNonLetter(in ComponentName who, int length, boolean parent);
int getPasswordMinimumNonLetter(in ComponentName who, int userHandle, boolean parent);
+ PasswordMetrics getPasswordMinimumMetrics(int userHandle);
+
void setPasswordHistoryLength(in ComponentName who, int length, boolean parent);
int getPasswordHistoryLength(in ComponentName who, int userHandle, boolean parent);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 464f75c..d9bfde5 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -16,41 +16,65 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE;
+import static com.android.internal.widget.PasswordValidationError.TOO_LONG;
+import static com.android.internal.widget.PasswordValidationError.TOO_SHORT;
+import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils.CredentialType;
import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
/**
* A class that represents the metrics of a credential that are used to decide whether or not a
- * credential meets the requirements. If the credential is a pattern, only quality matters.
+ * credential meets the requirements.
*
* {@hide}
*/
-public class PasswordMetrics implements Parcelable {
+public final class PasswordMetrics implements Parcelable {
+ private static final String TAG = "PasswordMetrics";
+
// Maximum allowed number of repeated or ordered characters in a sequence before we'll
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
- public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ public @CredentialType int credType;
+ // Fields below only make sense when credType is PASSWORD.
public int length = 0;
public int letters = 0;
public int upperCase = 0;
@@ -58,139 +82,62 @@
public int numeric = 0;
public int symbols = 0;
public int nonLetter = 0;
+ public int nonNumeric = 0;
+ // MAX_VALUE is the most relaxed value, any sequence is ok, e.g. 123456789. 4 would forbid it.
+ public int seqLength = Integer.MAX_VALUE;
- public PasswordMetrics() {}
-
- public PasswordMetrics(int quality) {
- this.quality = quality;
+ public PasswordMetrics(int credType) {
+ this.credType = credType;
}
- public PasswordMetrics(int quality, int length) {
- this.quality = quality;
+ public PasswordMetrics(int credType , int length, int letters, int upperCase, int lowerCase,
+ int numeric, int symbols, int nonLetter, int nonNumeric, int seqLength) {
+ this.credType = credType;
this.length = length;
- }
-
- public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase,
- int numeric, int symbols, int nonLetter) {
- this(quality, length);
this.letters = letters;
this.upperCase = upperCase;
this.lowerCase = lowerCase;
this.numeric = numeric;
this.symbols = symbols;
this.nonLetter = nonLetter;
+ this.nonNumeric = nonNumeric;
+ this.seqLength = seqLength;
}
- private PasswordMetrics(Parcel in) {
- quality = in.readInt();
- length = in.readInt();
- letters = in.readInt();
- upperCase = in.readInt();
- lowerCase = in.readInt();
- numeric = in.readInt();
- symbols = in.readInt();
- nonLetter = in.readInt();
- }
-
- /** Returns the min quality allowed by {@code complexityLevel}. */
- public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
- // this would be the quality of the first metrics since mMetrics is sorted in ascending
- // order of quality
- return PasswordComplexityBucket
- .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
- }
-
- /**
- * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
- * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
- * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
- * and {@code complexityLevel}.
- *
- * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
- * more than one set of metrics to meet the minimum complexity requirement and inspecting what
- * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
- * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
- * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
- * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
- * an alphanumeric password so we would update the min complexity required min length to 6.
- */
- public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
- int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
- boolean requiresLettersOrSymbols) {
- int targetQuality = Math.max(
- userEnteredPasswordQuality,
- getActualRequiredQuality(
- requestedQuality, requiresNumeric, requiresLettersOrSymbols));
- return getTargetQualityMetrics(complexityLevel, targetQuality);
- }
-
- /**
- * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
- * is the same as {@code targetQuality}.
- *
- * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
- * with the min quality at {@code complexityLevel}.
- */
- // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
- @VisibleForTesting
- public static PasswordMetrics getTargetQualityMetrics(
- @PasswordComplexity int complexityLevel, int targetQuality) {
- PasswordComplexityBucket targetBucket =
- PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
- for (PasswordMetrics metrics : targetBucket.mMetrics) {
- if (targetQuality == metrics.quality) {
- return metrics;
- }
- }
- // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
- // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
- // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
- // requested quality is NUMERIC
- return targetBucket.mMetrics[0];
- }
-
- /**
- * Finds out the actual quality requirement based on whether quality is {@link
- * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
- * required.
- */
- @VisibleForTesting
- // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
- public static int getActualRequiredQuality(
- int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
- if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
- return requestedQuality;
- }
-
- // find out actual password quality from complex requirements
- if (requiresNumeric && requiresLettersOrSymbols) {
- return PASSWORD_QUALITY_ALPHANUMERIC;
- }
- if (requiresLettersOrSymbols) {
- return PASSWORD_QUALITY_ALPHABETIC;
- }
- if (requiresNumeric) {
- // cannot specify numeric complex using complex quality so this must be numeric
- return PASSWORD_QUALITY_NUMERIC;
- }
-
- // reaching here means dpm sets quality to complex without specifying any requirements
- return PASSWORD_QUALITY_UNSPECIFIED;
+ private PasswordMetrics(PasswordMetrics other) {
+ this(other.credType, other.length, other.letters, other.upperCase, other.lowerCase,
+ other.numeric, other.symbols, other.nonLetter, other.nonNumeric, other.seqLength);
}
/**
* Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
* if {@code complexityLevel} is not valid.
+ *
+ * TODO: move to PasswordPolicy
*/
@PasswordComplexity
public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
- return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+ switch (complexityLevel) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ case PASSWORD_COMPLEXITY_LOW:
+ case PASSWORD_COMPLEXITY_NONE:
+ return complexityLevel;
+ default:
+ Log.w(TAG, "Invalid password complexity used: " + complexityLevel);
+ return PASSWORD_COMPLEXITY_NONE;
+ }
}
- public boolean isDefault() {
- return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
- && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
- && numeric == 0 && symbols == 0 && nonLetter == 0;
+ private static boolean hasInvalidCharacters(byte[] password) {
+ // Allow non-control Latin-1 characters only.
+ for (byte b : password) {
+ char c = (char) b;
+ if (c < 32 || c > 127) {
+ return true;
+ }
+ }
+ return false;
}
@Override
@@ -200,7 +147,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(quality);
+ dest.writeInt(credType);
dest.writeInt(length);
dest.writeInt(letters);
dest.writeInt(upperCase);
@@ -208,12 +155,25 @@
dest.writeInt(numeric);
dest.writeInt(symbols);
dest.writeInt(nonLetter);
+ dest.writeInt(nonNumeric);
+ dest.writeInt(seqLength);
}
- public static final @android.annotation.NonNull Parcelable.Creator<PasswordMetrics> CREATOR
+ public static final @NonNull Parcelable.Creator<PasswordMetrics> CREATOR
= new Parcelable.Creator<PasswordMetrics>() {
public PasswordMetrics createFromParcel(Parcel in) {
- return new PasswordMetrics(in);
+ int credType = in.readInt();
+ int length = in.readInt();
+ int letters = in.readInt();
+ int upperCase = in.readInt();
+ int lowerCase = in.readInt();
+ int numeric = in.readInt();
+ int symbols = in.readInt();
+ int nonLetter = in.readInt();
+ int nonNumeric = in.readInt();
+ int seqLength = in.readInt();
+ return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric,
+ symbols, nonLetter, nonNumeric, seqLength);
}
public PasswordMetrics[] newArray(int size) {
@@ -232,9 +192,9 @@
if (credential.isPassword()) {
return PasswordMetrics.computeForPassword(credential.getCredential());
} else if (credential.isPattern()) {
- return new PasswordMetrics(PASSWORD_QUALITY_SOMETHING);
+ return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
} else if (credential.isNone()) {
- return new PasswordMetrics(PASSWORD_QUALITY_UNSPECIFIED);
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
} else {
throw new IllegalArgumentException("Unknown credential type " + credential.getType());
}
@@ -251,16 +211,19 @@
int numeric = 0;
int symbols = 0;
int nonLetter = 0;
+ int nonNumeric = 0;
final int length = password.length;
for (byte b : password) {
switch (categoryChar((char) b)) {
case CHAR_LOWER_CASE:
letters++;
lowerCase++;
+ nonNumeric++;
break;
case CHAR_UPPER_CASE:
letters++;
upperCase++;
+ nonNumeric++;
break;
case CHAR_DIGIT:
numeric++;
@@ -269,53 +232,14 @@
case CHAR_SYMBOL:
symbols++;
nonLetter++;
+ nonNumeric++;
break;
}
}
- // Determine the quality of the password
- final boolean hasNumeric = numeric > 0;
- final boolean hasNonNumeric = (letters + symbols) > 0;
- final int quality;
- if (hasNonNumeric && hasNumeric) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- } else if (hasNonNumeric) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
- } else if (hasNumeric) {
- quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE
- ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- } else {
- quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- }
-
- return new PasswordMetrics(
- quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof PasswordMetrics)) {
- return false;
- }
- PasswordMetrics o = (PasswordMetrics) other;
- return this.quality == o.quality
- && this.length == o.length
- && this.letters == o.letters
- && this.upperCase == o.upperCase
- && this.lowerCase == o.lowerCase
- && this.numeric == o.numeric
- && this.symbols == o.symbols
- && this.nonLetter == o.nonLetter;
- }
-
- private boolean satisfiesBucket(PasswordMetrics... bucket) {
- for (PasswordMetrics metrics : bucket) {
- if (this.quality == metrics.quality) {
- return this.length >= metrics.length;
- }
- }
- return false;
+ final int seqLength = maxLengthSequence(password);
+ return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase,
+ numeric, symbols, nonLetter, nonNumeric, seqLength);
}
/**
@@ -400,108 +324,394 @@
}
}
- /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */
- @PasswordComplexity
- public int determineComplexity() {
- for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
- if (satisfiesBucket(bucket.mMetrics)) {
- return bucket.mComplexityLevel;
- }
+ /**
+ * Returns the weakest metrics that is stricter or equal to all given metrics.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static PasswordMetrics merge(List<PasswordMetrics> metrics) {
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ for (PasswordMetrics m : metrics) {
+ result.maxWith(m);
}
- return PASSWORD_COMPLEXITY_NONE;
+
+ return result;
}
/**
- * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
+ * Makes current metric at least as strong as {@code other} in every criterion.
+ *
+ * TODO: move to PasswordPolicy
*/
- private static class PasswordComplexityBucket {
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket HIGH =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_HIGH,
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 8),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
- 6));
+ private void maxWith(PasswordMetrics other) {
+ credType = Math.max(credType, other.credType);
+ if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ return;
+ }
+ length = Math.max(length, other.length);
+ letters = Math.max(letters, other.letters);
+ upperCase = Math.max(upperCase, other.upperCase);
+ lowerCase = Math.max(lowerCase, other.lowerCase);
+ numeric = Math.max(numeric, other.numeric);
+ symbols = Math.max(symbols, other.symbols);
+ nonLetter = Math.max(nonLetter, other.nonLetter);
+ nonNumeric = Math.max(nonNumeric, other.nonNumeric);
+ seqLength = Math.min(seqLength, other.seqLength);
+ }
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket MEDIUM =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_MEDIUM,
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 4),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
- 4));
+ /**
+ * Returns minimum password quality for a given complexity level.
+ *
+ * TODO: this function is used for determining allowed credential types, so it should return
+ * credential type rather than 'quality'.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static int complexityLevelToMinQuality(int complexity) {
+ switch (complexity) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ return PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ case PASSWORD_COMPLEXITY_LOW:
+ return PASSWORD_QUALITY_SOMETHING;
+ case PASSWORD_COMPLEXITY_NONE:
+ default:
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+ }
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket LOW =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
-
- /**
- * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
- */
- private static final PasswordComplexityBucket NONE =
- new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics());
-
- /** Array containing all buckets from high to low. */
- private static final PasswordComplexityBucket[] BUCKETS =
- new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW};
-
- @PasswordComplexity
- private final int mComplexityLevel;
- private final PasswordMetrics[] mMetrics;
-
- /**
- * @param metricsArray must be sorted in ascending order of {@link #quality}.
- */
- private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
- PasswordMetrics... metricsArray) {
- int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
- for (PasswordMetrics metrics : metricsArray) {
- if (metrics.quality < previousQuality) {
- throw new IllegalArgumentException("metricsArray must be sorted in ascending"
- + " order of quality");
- }
- previousQuality = metrics.quality;
+ /**
+ * Enum representing requirements for each complexity level.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private enum ComplexityBucket {
+ // Keep ordered high -> low.
+ BUCKET_HIGH(PASSWORD_COMPLEXITY_HIGH) {
+ @Override
+ boolean canHaveSequence() {
+ return false;
}
- this.mMetrics = metricsArray;
- this.mComplexityLevel = complexityLevel;
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return containsNonNumeric ? 6 : 8;
+ }
+ @Override
+ boolean allowsNumericPassword() {
+ return false;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType == CREDENTIAL_TYPE_PASSWORD;
+ }
+ },
+ BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) {
+ @Override
+ boolean canHaveSequence() {
+ return false;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 4;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return false;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType == CREDENTIAL_TYPE_PASSWORD;
+ }
+ },
+ BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) {
+ @Override
+ boolean canHaveSequence() {
+ return true;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 0;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return true;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType != CREDENTIAL_TYPE_NONE;
+ }
+ },
+ BUCKET_NONE(PASSWORD_COMPLEXITY_NONE) {
+ @Override
+ boolean canHaveSequence() {
+ return true;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 0;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return true;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return true;
+ }
+ };
+
+ int mComplexityLevel;
+
+ abstract boolean canHaveSequence();
+ abstract int getMinimumLength(boolean containsNonNumeric);
+ abstract boolean allowsNumericPassword();
+ abstract boolean allowsCredType(int credType);
+
+ ComplexityBucket(int complexityLevel) {
+ this.mComplexityLevel = complexityLevel;
}
- /** Returns the bucket that {@code complexityLevel} represents. */
- private static PasswordComplexityBucket complexityLevelToBucket(
- @PasswordComplexity int complexityLevel) {
- for (PasswordComplexityBucket bucket : BUCKETS) {
+ static ComplexityBucket forComplexity(int complexityLevel) {
+ for (ComplexityBucket bucket : values()) {
if (bucket.mComplexityLevel == complexityLevel) {
return bucket;
}
}
- return NONE;
+ throw new IllegalArgumentException("Invalid complexity level: " + complexityLevel);
}
}
+
+ /**
+ * Returns whether current metrics satisfies a given complexity bucket.
+ *
+ * TODO: move inside ComplexityBucket.
+ */
+ private boolean satisfiesBucket(ComplexityBucket bucket) {
+ if (!bucket.allowsCredType(credType)) {
+ return false;
+ }
+ if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ return true;
+ }
+ return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE)
+ && length >= bucket.getMinimumLength(nonNumeric > 0 /* hasNonNumeric */);
+ }
+
+ /**
+ * Returns the maximum complexity level satisfied by password with this metrics.
+ *
+ * TODO: move inside ComplexityBucket.
+ */
+ public int determineComplexity() {
+ for (ComplexityBucket bucket : ComplexityBucket.values()) {
+ if (satisfiesBucket(bucket)) {
+ return bucket.mComplexityLevel;
+ }
+ }
+ throw new IllegalStateException("Failed to figure out complexity for a given metrics");
+ }
+
+ /**
+ * Validates password against minimum metrics and complexity.
+ *
+ * @param adminMetrics - minimum metrics to satisfy admin requirements.
+ * @param minComplexity - minimum complexity imposed by the requester.
+ * @param isPin - whether it is PIN that should be only digits
+ * @param password - password to validate.
+ * @return a list of password validation errors. An empty list means the password is OK.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static List<PasswordValidationError> validatePassword(
+ PasswordMetrics adminMetrics, int minComplexity, boolean isPin, byte[] password) {
+
+ if (hasInvalidCharacters(password)) {
+ return Collections.singletonList(
+ new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
+ }
+
+ final PasswordMetrics enteredMetrics = computeForPassword(password);
+ return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
+ }
+
+ /**
+ * Validates password metrics against minimum metrics and complexity
+ *
+ * @param adminMetrics - minimum metrics to satisfy admin requirements.
+ * @param minComplexity - minimum complexity imposed by the requester.
+ * @param isPin - whether it is PIN that should be only digits
+ * @param actualMetrics - metrics for password to validate.
+ * @return a list of password validation errors. An empty list means the password is OK.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static List<PasswordValidationError> validatePasswordMetrics(
+ PasswordMetrics adminMetrics, int minComplexity, boolean isPin,
+ PasswordMetrics actualMetrics) {
+ final ComplexityBucket bucket = ComplexityBucket.forComplexity(minComplexity);
+
+ // Make sure credential type is satisfactory.
+ // TODO: stop relying on credential type ordering.
+ if (actualMetrics.credType < adminMetrics.credType
+ || !bucket.allowsCredType(actualMetrics.credType)) {
+ return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
+ }
+ // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added.
+ if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) {
+ return Collections.emptyList(); // Nothing to check for pattern or none.
+ }
+
+ if (isPin && actualMetrics.nonNumeric > 0) {
+ return Collections.singletonList(
+ new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
+ }
+
+ final ArrayList<PasswordValidationError> result = new ArrayList<>();
+ if (actualMetrics.length > MAX_PASSWORD_LENGTH) {
+ result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH));
+ }
+
+ final PasswordMetrics minMetrics = applyComplexity(adminMetrics, isPin, bucket);
+
+ // Clamp required length between maximum and minimum valid values.
+ minMetrics.length = Math.min(MAX_PASSWORD_LENGTH,
+ Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE));
+ minMetrics.removeOverlapping();
+
+ comparePasswordMetrics(minMetrics, actualMetrics, result);
+
+ return result;
+ }
+
+ /**
+ * TODO: move to PasswordPolicy
+ */
+ private static void comparePasswordMetrics(PasswordMetrics minMetrics,
+ PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) {
+ if (actualMetrics.length < minMetrics.length) {
+ result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length));
+ }
+ if (actualMetrics.letters < minMetrics.letters) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters));
+ }
+ if (actualMetrics.upperCase < minMetrics.upperCase) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_UPPER_CASE, minMetrics.upperCase));
+ }
+ if (actualMetrics.lowerCase < minMetrics.lowerCase) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_LOWER_CASE, minMetrics.lowerCase));
+ }
+ if (actualMetrics.numeric < minMetrics.numeric) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_DIGITS, minMetrics.numeric));
+ }
+ if (actualMetrics.symbols < minMetrics.symbols) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_SYMBOLS, minMetrics.symbols));
+ }
+ if (actualMetrics.nonLetter < minMetrics.nonLetter) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_NON_LETTER, minMetrics.nonLetter));
+ }
+ if (actualMetrics.nonNumeric < minMetrics.nonNumeric) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_NON_DIGITS, minMetrics.nonNumeric));
+ }
+ if (actualMetrics.seqLength > minMetrics.seqLength) {
+ result.add(new PasswordValidationError(CONTAINS_SEQUENCE, 0));
+ }
+ }
+
+ /**
+ * Drop requirements that are superseded by others, e.g. if it is required to have 5 upper case
+ * letters and 5 lower case letters, there is no need to require minimum number of letters to
+ * be 10 since it will be fulfilled once upper and lower case requirements are fulfilled.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private void removeOverlapping() {
+ // upperCase + lowerCase can override letters
+ final int indirectLetters = upperCase + lowerCase;
+
+ // numeric + symbols can override nonLetter
+ final int indirectNonLetter = numeric + symbols;
+
+ // letters + symbols can override nonNumeric
+ final int effectiveLetters = Math.max(letters, indirectLetters);
+ final int indirectNonNumeric = effectiveLetters + symbols;
+
+ // letters + nonLetters can override length
+ // numeric + nonNumeric can also override length, so max it with previous.
+ final int effectiveNonLetter = Math.max(nonLetter, indirectNonLetter);
+ final int effectiveNonNumeric = Math.max(nonNumeric, indirectNonNumeric);
+ final int indirectLength = Math.max(effectiveLetters + effectiveNonLetter,
+ numeric + effectiveNonNumeric);
+
+ if (indirectLetters >= letters) {
+ letters = 0;
+ }
+ if (indirectNonLetter >= nonLetter) {
+ nonLetter = 0;
+ }
+ if (indirectNonNumeric >= nonNumeric) {
+ nonNumeric = 0;
+ }
+ if (indirectLength >= length) {
+ length = 0;
+ }
+ }
+
+ /**
+ * Combine minimum metrics, set by admin, complexity set by the requester and actual entered
+ * password metrics to get resulting minimum metrics that the password has to satisfy. Always
+ * returns a new PasswordMetrics object.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private static PasswordMetrics applyComplexity(
+ PasswordMetrics adminMetrics, boolean isPin, ComplexityBucket bucket) {
+ final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics);
+
+ if (!bucket.canHaveSequence()) {
+ minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE);
+ }
+
+ minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin));
+
+ if (!isPin && !bucket.allowsNumericPassword()) {
+ minMetrics.nonNumeric = Math.max(minMetrics.nonNumeric, 1);
+ }
+
+ return minMetrics;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final PasswordMetrics that = (PasswordMetrics) o;
+ return credType == that.credType
+ && length == that.length
+ && letters == that.letters
+ && upperCase == that.upperCase
+ && lowerCase == that.lowerCase
+ && numeric == that.numeric
+ && symbols == that.symbols
+ && nonLetter == that.nonLetter
+ && nonNumeric == that.nonNumeric
+ && seqLength == that.seqLength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(credType, length, letters, upperCase, lowerCase, numeric, symbols,
+ nonLetter, nonNumeric, seqLength);
+ }
}
diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java
new file mode 100644
index 0000000..13f11ad
--- /dev/null
+++ b/core/java/android/app/admin/PasswordPolicy.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+/**
+ * {@hide}
+ */
+public class PasswordPolicy {
+ public static final int DEF_MINIMUM_LENGTH = 0;
+ public static final int DEF_MINIMUM_LETTERS = 1;
+ public static final int DEF_MINIMUM_UPPER_CASE = 0;
+ public static final int DEF_MINIMUM_LOWER_CASE = 0;
+ public static final int DEF_MINIMUM_NUMERIC = 1;
+ public static final int DEF_MINIMUM_SYMBOLS = 1;
+ public static final int DEF_MINIMUM_NON_LETTER = 0;
+
+ public int quality = PASSWORD_QUALITY_UNSPECIFIED;
+ public int length = DEF_MINIMUM_LENGTH;
+ public int letters = DEF_MINIMUM_LETTERS;
+ public int upperCase = DEF_MINIMUM_UPPER_CASE;
+ public int lowerCase = DEF_MINIMUM_LOWER_CASE;
+ public int numeric = DEF_MINIMUM_NUMERIC;
+ public int symbols = DEF_MINIMUM_SYMBOLS;
+ public int nonLetter = DEF_MINIMUM_NON_LETTER;
+
+ /**
+ * Returns a minimum password metrics that the password should have to satisfy current policy.
+ */
+ public PasswordMetrics getMinMetrics() {
+ if (quality == PASSWORD_QUALITY_UNSPECIFIED) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ } else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK
+ || quality == PASSWORD_QUALITY_SOMETHING) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ } // quality is NUMERIC or stronger.
+
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ result.length = length;
+
+ if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+ result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
+ } else if (quality == PASSWORD_QUALITY_ALPHABETIC) {
+ result.nonNumeric = 1;
+ } else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) {
+ result.numeric = 1;
+ result.nonNumeric = 1;
+ } else if (quality == PASSWORD_QUALITY_COMPLEX) {
+ result.numeric = numeric;
+ result.letters = letters;
+ result.upperCase = upperCase;
+ result.lowerCase = lowerCase;
+ result.nonLetter = nonLetter;
+ result.symbols = symbols;
+ }
+ return result;
+ }
+}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index b3260c4..024afe2 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -154,12 +154,6 @@
public abstract int[] getIdleUidsForUser(@UserIdInt int userId);
/**
- * @return True if currently app idle parole mode is on. This means all idle apps are allow to
- * run for a short period of time.
- */
- public abstract boolean isAppIdleParoleOn();
-
- /**
* Sets up a listener for changes to packages being accessed.
* @param listener A listener within the system process.
*/
@@ -180,12 +174,6 @@
boolean idle, int bucket, int reason);
/**
- * Callback to inform listeners that the parole state has changed. This means apps are
- * allowed to do work even if they're idle or in a low bucket.
- */
- public abstract void onParoleStateChanged(boolean isParoleOn);
-
- /**
* Optional callback to inform the listener that the app has transitioned into
* an active state due to user interaction.
*/
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 9dbfbc7..02b6b3e 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1469,9 +1469,8 @@
* This method can be called from multiple threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
- * @param uri The content:// URI of the insertion request. This must not be {@code null}.
+ * @param uri The content:// URI of the insertion request.
* @param values A set of column_name/value pairs to add to the database.
- * This must not be {@code null}.
* @return The URI for the newly inserted item.
*/
@Override
@@ -1538,7 +1537,6 @@
* @param uri The URI to query. This can potentially have a record ID if this
* is an update request for a specific record.
* @param values A set of column_name/value pairs to update in the database.
- * This must not be {@code null}.
* @param selection An optional filter to match rows to update.
* @return the number of rows affected.
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index e7e278f..3eb066e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -63,6 +63,7 @@
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.provider.MediaStore;
+import android.telephony.TelephonyRegistryManager;
import android.util.AttributeSet;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -4716,7 +4717,7 @@
/**
* Use with {@link #getSystemService(String)} to retrieve an
- * {@link android.os.telephony.TelephonyRegistryManager}.
+ * {@link TelephonyRegistryManager}.
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/pm/AndroidTestBaseUpdater.java b/core/java/android/content/pm/AndroidTestBaseUpdater.java
index 8fcfe71..18d3ba3 100644
--- a/core/java/android/content/pm/AndroidTestBaseUpdater.java
+++ b/core/java/android/content/pm/AndroidTestBaseUpdater.java
@@ -55,15 +55,20 @@
private static final long REMOVE_ANDROID_TEST_BASE = 133396946L;
private static boolean isChangeEnabled(Package pkg) {
- IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- try {
- return platformCompat.isChangeEnabled(REMOVE_ANDROID_TEST_BASE, pkg.applicationInfo);
- } catch (RemoteException | NullPointerException e) {
- Log.e(TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+ // Do not ask platform compat for system apps to prevent a boot time regression in tests.
+ // b/142558883.
+ if (!pkg.applicationInfo.isSystemApp()) {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ try {
+ return platformCompat.isChangeEnabled(REMOVE_ANDROID_TEST_BASE,
+ pkg.applicationInfo);
+ } catch (RemoteException | NullPointerException e) {
+ Log.e(TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+ }
}
// Fall back to previous behaviour.
- return pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.Q;
+ return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.Q;
}
@Override
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 065ee0b..800c15c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10863,16 +10863,13 @@
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
- * "idle_duration=5000,parole_interval=4500,screen_thresholds=0/0/60000/120000"
+ * "idle_duration=5000,prediction_timeout=4500,screen_thresholds=0/0/60000/120000"
* <p>
* All durations are in millis.
* Array values are separated by forward slashes
* The following keys are supported:
*
* <pre>
- * parole_interval (long)
- * parole_window (long)
- * parole_duration (long)
* screen_thresholds (long[4])
* elapsed_thresholds (long[4])
* strong_usage_duration (long)
@@ -10883,17 +10880,12 @@
* exempted_sync_duration (long)
* system_interaction_duration (long)
* initial_foreground_service_start_duration (long)
- * stable_charging_threshold (long)
- *
- * idle_duration (long) // This is deprecated and used to circumvent b/26355386.
- * idle_duration2 (long) // deprecated
- * wallclock_threshold (long) // deprecated
* </pre>
*
* <p>
* Type: string
* @hide
- * @see com.android.server.usage.UsageStatsService.SettingsObserver
+ * @see com.android.server.usage.AppStandbyController
*/
public static final String APP_IDLE_CONSTANTS = "app_idle_constants";
diff --git a/core/java/android/service/carrier/CarrierService.java b/core/java/android/service/carrier/CarrierService.java
index 9184d6d..eefc1b7 100644
--- a/core/java/android/service/carrier/CarrierService.java
+++ b/core/java/android/service/carrier/CarrierService.java
@@ -22,7 +22,7 @@
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
-import android.os.telephony.TelephonyRegistryManager;
+import android.telephony.TelephonyRegistryManager;
import android.util.Log;
/**
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
similarity index 99%
rename from telephony/java/android/telephony/PhoneStateListener.java
rename to core/java/android/telephony/PhoneStateListener.java
index 1ba0a41..a65c8fd 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -35,8 +35,8 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.annotations.VisibleForTesting;
import dalvik.system.VMRuntime;
diff --git a/core/java/android/os/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
similarity index 75%
rename from core/java/android/os/telephony/TelephonyRegistryManager.java
rename to core/java/android/telephony/TelephonyRegistryManager.java
index 1332331..64d6124 100644
--- a/core/java/android/os/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -13,12 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.os.telephony;
+package android.telephony;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.content.Context;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.Annotation.ApnType;
@@ -40,8 +46,15 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.ims.ImsReasonInfo;
+import android.util.Log;
+
import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
/**
* A centralized place to notify telephony related status changes, e.g, {@link ServiceState} update
@@ -58,9 +71,26 @@
private static final String TAG = "TelephonyRegistryManager";
private static ITelephonyRegistry sRegistry;
+ private final Context mContext;
+
+ /**
+ * A mapping between {@link SubscriptionManager.OnSubscriptionsChangedListener} and
+ * its callback IOnSubscriptionsChangedListener.
+ */
+ private final Map<SubscriptionManager.OnSubscriptionsChangedListener,
+ IOnSubscriptionsChangedListener> mSubscriptionChangedListenerMap = new HashMap<>();
+ /**
+ * A mapping between {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} and
+ * its callback IOnSubscriptionsChangedListener.
+ */
+ private final Map<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener,
+ IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap
+ = new HashMap<>();
+
/** @hide **/
- public TelephonyRegistryManager() {
+ public TelephonyRegistryManager(@NonNull Context context) {
+ mContext = context;
if (sRegistry == null) {
sRegistry = ITelephonyRegistry.Stub.asInterface(
ServiceManager.getService("telephony.registry"));
@@ -68,6 +98,113 @@
}
/**
+ * Register for changes to the list of active {@link SubscriptionInfo} records or to the
+ * individual records themselves. When a change occurs the onSubscriptionsChanged method of
+ * the listener will be invoked immediately if there has been a notification. The
+ * onSubscriptionChanged method will also be triggered once initially when calling this
+ * function.
+ *
+ * @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener}
+ * with onSubscriptionsChanged overridden.
+ * @param executor the executor that will execute callbacks.
+ */
+ public void addOnSubscriptionsChangedListener(
+ @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener,
+ @NonNull Executor executor) {
+ IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
+ @Override
+ public void onSubscriptionsChanged () {
+ Log.d(TAG, "onSubscriptionsChangedListener callback received.");
+ executor.execute(() -> listener.onSubscriptionsChanged());
+ }
+ };
+ mSubscriptionChangedListenerMap.put(listener, callback);
+ try {
+ sRegistry.addOnSubscriptionsChangedListener(mContext.getOpPackageName(), callback);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
+ /**
+ * Unregister the {@link SubscriptionManager.OnSubscriptionsChangedListener}. This is not
+ * strictly necessary as the listener will automatically be unregistered if an attempt to
+ * invoke the listener fails.
+ *
+ * @param listener that is to be unregistered.
+ */
+ public void removeOnSubscriptionsChangedListener(
+ @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener) {
+ if (mSubscriptionChangedListenerMap.get(listener) == null) {
+ return;
+ }
+ try {
+ sRegistry.removeOnSubscriptionsChangedListener(mContext.getOpPackageName(),
+ mSubscriptionChangedListenerMap.get(listener));
+ mSubscriptionChangedListenerMap.remove(listener);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
+ /**
+ * Register for changes to the list of opportunistic subscription records or to the
+ * individual records themselves. When a change occurs the onOpportunisticSubscriptionsChanged
+ * method of the listener will be invoked immediately if there has been a notification.
+ *
+ * @param listener an instance of
+ * {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} with
+ * onOpportunisticSubscriptionsChanged overridden.
+ * @param executor an Executor that will execute callbacks.
+ */
+ public void addOnOpportunisticSubscriptionsChangedListener(
+ @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener,
+ @NonNull Executor executor) {
+ /**
+ * The callback methods need to be called on the executor thread where
+ * this object was created. If the binder did that for us it'd be nice.
+ */
+ IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
+ @Override
+ public void onSubscriptionsChanged() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Log.d(TAG, "onOpportunisticSubscriptionsChanged callback received.");
+ executor.execute(() -> listener.onOpportunisticSubscriptionsChanged());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ };
+ mOpportunisticSubscriptionChangedListenerMap.put(listener, callback);
+ try {
+ sRegistry.addOnOpportunisticSubscriptionsChangedListener(mContext.getOpPackageName(),
+ callback);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
+ /**
+ * Unregister the {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener}
+ * that is currently listening opportunistic subscriptions change. This is not strictly
+ * necessary as the listener will automatically be unregistered if an attempt to invoke the
+ * listener fails.
+ *
+ * @param listener that is to be unregistered.
+ */
+ public void removeOnOpportunisticSubscriptionsChangedListener(
+ @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) {
+ try {
+ sRegistry.removeOnSubscriptionsChangedListener(mContext.getOpPackageName(),
+ mOpportunisticSubscriptionChangedListenerMap.get(listener));
+ mOpportunisticSubscriptionChangedListenerMap.remove(listener);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
+ /**
* Informs the system of an intentional upcoming carrier network change by a carrier app.
* This call only used to allow the system to provide alternative UI while telephony is
* performing an action that may result in intentional, temporary network lack of connectivity.
@@ -546,4 +683,15 @@
}
}
+ /**
+ * @param activeDataSubId
+ * @hide
+ */
+ public void notifyActiveDataSubIdChanged(int activeDataSubId) {
+ try {
+ sRegistry.notifyActiveDataSubIdChanged(activeDataSubId);
+ } catch (RemoteException ex) {
+
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 0817452..cc28840 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -707,10 +707,10 @@
try {
services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ Log.i(LOG_TAG, "Enabled AccessibilityServices " + services);
}
} catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ Log.e(LOG_TAG, "Error while obtaining the enabled AccessibilityServices. ", re);
}
if (mAccessibilityPolicy != null) {
services = mAccessibilityPolicy.getEnabledAccessibilityServiceList(
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 158700b..363e549 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -184,6 +184,12 @@
System.loadLibrary("android");
System.loadLibrary("compiler_rt");
System.loadLibrary("jnigraphics");
+
+ try {
+ System.loadLibrary("sfplugin_ccodec");
+ } catch (Error | RuntimeException e) {
+ // tolerate missing sfplugin_ccodec which is only present on Codec 2 devices
+ }
}
native private static void nativePreloadAppProcessHALs();
diff --git a/telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl b/core/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl
similarity index 100%
rename from telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl
rename to core/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
similarity index 99%
rename from telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
rename to core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 90019ee..084a3cc 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -29,6 +29,9 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
+/**
+ * {@hide}
+ */
oneway interface IPhoneStateListener {
void onServiceStateChanged(in ServiceState serviceState);
void onSignalStrengthChanged(int asu);
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
similarity index 100%
rename from telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
rename to core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 1daa25a..8fea703 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -30,6 +30,7 @@
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PasswordMetrics;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.ComponentName;
@@ -58,10 +59,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.google.android.collect.Lists;
-
import libcore.util.HexEncoding;
+import com.google.android.collect.Lists;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
@@ -77,7 +78,6 @@
* Utilities for the lock pattern and its settings.
*/
public class LockPatternUtils {
-
private static final String TAG = "LockPatternUtils";
private static final boolean FRP_CREDENTIAL_ENABLED = true;
@@ -114,6 +114,7 @@
*/
public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
+ // NOTE: When modifying this, make sure credential sufficiency validation logic is intact.
public static final int CREDENTIAL_TYPE_NONE = -1;
public static final int CREDENTIAL_TYPE_PATTERN = 1;
public static final int CREDENTIAL_TYPE_PASSWORD = 2;
@@ -289,10 +290,10 @@
return getDevicePolicyManager().getPasswordMaximumLength(quality);
}
- /**
- * Gets the device policy password mode. If the mode is non-specific, returns
- * MODE_PATTERN which allows the user to choose anything.
- */
+ public PasswordMetrics getRequestedPasswordMetrics(int userId) {
+ return getDevicePolicyManager().getPasswordMinimumMetrics(userId);
+ }
+
public int getRequestedPasswordQuality(int userId) {
return getDevicePolicyManager().getPasswordQuality(null, userId);
}
diff --git a/core/java/com/android/internal/widget/PasswordValidationError.java b/core/java/com/android/internal/widget/PasswordValidationError.java
new file mode 100644
index 0000000..41b234e
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordValidationError.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Password validation error containing an error code and optional requirement.
+ */
+public class PasswordValidationError {
+ // Password validation error codes
+ public static final int WEAK_CREDENTIAL_TYPE = 1;
+ public static final int CONTAINS_INVALID_CHARACTERS = 2;
+ public static final int TOO_SHORT = 3;
+ public static final int TOO_LONG = 4;
+ public static final int CONTAINS_SEQUENCE = 5;
+ public static final int NOT_ENOUGH_LETTERS = 6;
+ public static final int NOT_ENOUGH_UPPER_CASE = 7;
+ public static final int NOT_ENOUGH_LOWER_CASE = 8;
+ public static final int NOT_ENOUGH_DIGITS = 9;
+ public static final int NOT_ENOUGH_SYMBOLS = 10;
+ public static final int NOT_ENOUGH_NON_LETTER = 11;
+ public static final int NOT_ENOUGH_NON_DIGITS = 12;
+ public static final int RECENTLY_USED = 13;
+ // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings.
+
+ public final int errorCode;
+ public final int requirement;
+
+ public PasswordValidationError(int errorCode) {
+ this(errorCode, 0);
+ }
+
+ public PasswordValidationError(int errorCode, int requirement) {
+ this.errorCode = errorCode;
+ this.requirement = requirement;
+ }
+
+ @Override
+ public String toString() {
+ return errorCodeToString(errorCode) + (requirement > 0 ? "; required: " + requirement : "");
+ }
+
+ /**
+ * Returns textual representation of the error for logging purposes.
+ */
+ private static String errorCodeToString(int error) {
+ switch (error) {
+ case WEAK_CREDENTIAL_TYPE: return "Weak credential type";
+ case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character";
+ case TOO_SHORT: return "Password too short";
+ case TOO_LONG: return "Password too long";
+ case CONTAINS_SEQUENCE: return "Sequence too long";
+ case NOT_ENOUGH_LETTERS: return "Too few letters";
+ case NOT_ENOUGH_UPPER_CASE: return "Too few upper case letters";
+ case NOT_ENOUGH_LOWER_CASE: return "Too few lower case letters";
+ case NOT_ENOUGH_DIGITS: return "Too few numeric characters";
+ case NOT_ENOUGH_SYMBOLS: return "Too few symbols";
+ case NOT_ENOUGH_NON_LETTER: return "Too few non-letter characters";
+ case NOT_ENOUGH_NON_DIGITS: return "Too few non-numeric characters";
+ case RECENTLY_USED: return "Pin or password was recently used";
+ default: return "Unknown error " + error;
+ }
+ }
+
+}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 89c12f8..0487e13 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -9,6 +9,7 @@
#include "SkColorSpace.h"
#include "GraphicsJNI.h"
#include "SkStream.h"
+#include "SkWebpEncoder.h"
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
@@ -526,27 +527,14 @@
enum JavaEncodeFormat {
kJPEG_JavaEncodeFormat = 0,
kPNG_JavaEncodeFormat = 1,
- kWEBP_JavaEncodeFormat = 2
+ kWEBP_JavaEncodeFormat = 2,
+ kWEBP_LOSSY_JavaEncodeFormat = 3,
+ kWEBP_LOSSLESS_JavaEncodeFormat = 4,
};
static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle,
jint format, jint quality,
jobject jstream, jbyteArray jstorage) {
- SkEncodedImageFormat fm;
- switch (format) {
- case kJPEG_JavaEncodeFormat:
- fm = SkEncodedImageFormat::kJPEG;
- break;
- case kPNG_JavaEncodeFormat:
- fm = SkEncodedImageFormat::kPNG;
- break;
- case kWEBP_JavaEncodeFormat:
- fm = SkEncodedImageFormat::kWEBP;
- break;
- default:
- return JNI_FALSE;
- }
-
LocalScopedBitmap bitmap(bitmapHandle);
if (!bitmap.valid()) {
return JNI_FALSE;
@@ -577,6 +565,30 @@
}
skbitmap = p3;
}
+ SkEncodedImageFormat fm;
+ switch (format) {
+ case kJPEG_JavaEncodeFormat:
+ fm = SkEncodedImageFormat::kJPEG;
+ break;
+ case kPNG_JavaEncodeFormat:
+ fm = SkEncodedImageFormat::kPNG;
+ break;
+ case kWEBP_JavaEncodeFormat:
+ fm = SkEncodedImageFormat::kWEBP;
+ break;
+ case kWEBP_LOSSY_JavaEncodeFormat:
+ case kWEBP_LOSSLESS_JavaEncodeFormat: {
+ SkWebpEncoder::Options options;
+ options.fQuality = quality;
+ options.fCompression = format == kWEBP_LOSSY_JavaEncodeFormat ?
+ SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless;
+ return SkWebpEncoder::Encode(strm.get(), skbitmap.pixmap(), options) ?
+ JNI_TRUE : JNI_FALSE;
+ }
+ default:
+ return JNI_FALSE;
+ }
+
return SkEncodeImage(strm.get(), skbitmap, fm, quality) ? JNI_TRUE : JNI_FALSE;
}
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index ec91cbf..4d907f6 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -374,16 +374,17 @@
if (scale || jsubset) {
int translateX = 0;
int translateY = 0;
+ SkImageInfo scaledInfo;
if (jsubset) {
SkIRect subset;
GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
- translateX = -subset.fLeft;
- translateY = -subset.fTop;
- desiredWidth = subset.width();
- desiredHeight = subset.height();
+ translateX = -subset.fLeft;
+ translateY = -subset.fTop;
+ scaledInfo = bitmapInfo.makeWH(subset.width(), subset.height());
+ } else {
+ scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
}
- SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
SkBitmap scaledBm;
if (!scaledBm.setInfo(scaledInfo)) {
doThrowIOE(env, "Failed scaled setInfo");
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 0fada1b..49c5cad 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -35,7 +35,6 @@
#include <limits>
#include <memory>
#include <string>
-#include <unordered_map>
#include <vector>
#include "core_jni_helpers.h"
@@ -62,45 +61,26 @@
using namespace android;
-static const bool kDebugPolicy = false;
-static const bool kDebugProc = false;
+static constexpr bool kDebugPolicy = false;
+static constexpr bool kDebugProc = false;
// Stack reservation for reading small proc files. Most callers of
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static const ssize_t kProcReadStackBufferSize = 1024;
+static constexpr ssize_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static const ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
static pthread_key_t gBgKey = -1;
#endif
-/*
- * cpuset/sched aggregate profile mappings
- */
-static const std::unordered_map<int, std::string> kCpusetProfileMap = {
- {SP_DEFAULT, "CPUSET_SP_DEFAULT"}, {SP_BACKGROUND, "CPUSET_SP_BACKGROUND"},
- {SP_FOREGROUND, "CPUSET_SP_FOREGROUND"},{SP_SYSTEM, "CPUSET_SP_SYSTEM"},
- {SP_AUDIO_APP, "CPUSET_SP_FOREGROUND"}, {SP_AUDIO_SYS, "CPUSET_SP_FOREGROUND"},
- {SP_TOP_APP, "CPUSET_SP_TOP_APP"}, {SP_RT_APP, "CPUSET_SP_DEFAULT"},
- {SP_RESTRICTED, "CPUSET_SP_RESTRICTED"}
-};
-
-static const std::unordered_map<int, std::string> kSchedProfileMap = {
- {SP_DEFAULT, "SCHED_SP_DEFAULT"}, {SP_BACKGROUND, "SCHED_SP_BACKGROUND"},
- {SP_FOREGROUND, "SCHED_SP_FOREGROUND"}, {SP_SYSTEM, "SCHED_SP_DEFAULT"},
- {SP_AUDIO_APP, "SCHED_SP_FOREGROUND"}, {SP_AUDIO_SYS, "SCHED_SP_FOREGROUND"},
- {SP_TOP_APP, "SCHED_SP_TOP_APP"}, {SP_RT_APP, "SCHED_SP_RT_APP"},
- {SP_RESTRICTED, "SCHED_SP_DEFAULT"}
-};
-
// For both of these, err should be in the errno range (positive), not a status_t (negative)
static void signalExceptionForError(JNIEnv* env, int err, int tid) {
switch (err) {
@@ -227,7 +207,7 @@
return;
}
- int res = SetTaskProfiles(tid, {kSchedProfileMap.at(grp)}, true) ? 0 : -1;
+ int res = SetTaskProfiles(tid, {get_sched_policy_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
@@ -241,7 +221,7 @@
return;
}
- int res = SetTaskProfiles(tid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ int res = SetTaskProfiles(tid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
@@ -328,7 +308,7 @@
if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
// This task wants to stay at background
// update its cpuset so it doesn't only run on bg core(s)
- err = SetTaskProfiles(t_pid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ err = SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (err != NO_ERROR) {
signalExceptionForGroupError(env, -err, t_pid);
break;
@@ -337,7 +317,7 @@
}
}
- err = SetTaskProfiles(t_pid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ err = SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (err != NO_ERROR) {
signalExceptionForGroupError(env, -err, t_pid);
break;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 15b98af..06040a5 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -43,7 +43,7 @@
reserved 15; // next_heartbeat
reserved 16; // last_heartbeat_time_millis
reserved 17; // next_heartbeat_time_millis
- optional bool in_parole = 18;
+ reserved 18; // in_parole
optional bool in_thermal = 19;
repeated int32 started_users = 2;
@@ -534,7 +534,7 @@
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional bool is_charging = 1;
- optional bool is_in_parole = 2;
+ reserved 2; // is_in_parole
optional int64 elapsed_realtime = 6;
// List of UIDs currently in the foreground.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 14f5d97..acaaeec 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3701,6 +3701,8 @@
<flag name="flagRequestFingerprintGestures" value="0x00000200" />
<!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK}. -->
<flag name="flagRequestShortcutWarningDialogSpokenFeedback" value="0x00000400" />
+ <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_HANDLE_SHORTCUT}. -->
+ <flag name="flagHandleShortcut" value="0x00000800" />
</attr>
<!-- Component name of an activity that allows the user to modify
the settings for this service. This setting cannot be changed at runtime. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5605246..11efabb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -758,6 +758,9 @@
<!-- Indicates that p2p MAC randomization is supported on this device -->
<bool translatable="false" name="config_wifi_p2p_mac_randomization_supported">false</bool>
+ <!-- Indicates that AP mode MAC randomization is supported on this device -->
+ <bool translatable="false" name="config_wifi_ap_mac_randomization_supported">true</bool>
+
<!-- flag for activating paranoid MAC randomization on a limited set of SSIDs -->
<bool translatable="false" name="config_wifi_aggressive_randomization_ssid_whitelist_enabled">false</bool>
@@ -4319,11 +4322,11 @@
<!-- Trigger a warning for notifications with RemoteView objects that are larger in bytes than
this value (default 1MB)-->
- <integer name="config_notificationWarnRemoteViewSizeBytes">1000000</integer>
+ <integer name="config_notificationWarnRemoteViewSizeBytes">2000000</integer>
<!-- Strip notification RemoteView objects that are larger in bytes than this value (also log)
(default 2MB) -->
- <integer name="config_notificationStripRemoteViewSizeBytes">2000000</integer>
+ <integer name="config_notificationStripRemoteViewSizeBytes">5000000</integer>
<!-- Contains a blacklist of apps that should not get pre-installed carrier app permission
grants, even if the UICC claims that the app should be privileged. See b/138150105 -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 42cd2cd..6371d80 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1970,6 +1970,7 @@
<java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" />
<java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" />
<java-symbol type="bool" name="config_wifi_p2p_mac_randomization_supported" />
+ <java-symbol type="bool" name="config_wifi_ap_mac_randomization_supported" />
<java-symbol type="bool" name="config_wifi_aggressive_randomization_ssid_whitelist_enabled" />
<java-symbol type="bool" name="config_wifi_link_probing_supported" />
<java-symbol type="bool" name="config_wifi_fast_bss_transition_enabled" />
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 4ae9494..fb0dd46 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,52 +20,44 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
-import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
-import static android.app.admin.PasswordMetrics.getMinimumMetrics;
-import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
+import static android.app.admin.PasswordMetrics.validatePasswordMetrics;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.PasswordValidationError;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
/** Unit tests for {@link PasswordMetrics}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class PasswordMetricsTest {
-
- @Test
- public void testIsDefault() {
- final PasswordMetrics metrics = new PasswordMetrics();
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, metrics.quality);
- assertEquals(0, metrics.length);
- assertEquals(0, metrics.letters);
- assertEquals(0, metrics.upperCase);
- assertEquals(0, metrics.lowerCase);
- assertEquals(0, metrics.numeric);
- assertEquals(0, metrics.symbols);
- assertEquals(0, metrics.nonLetter);
- }
-
@Test
public void testParceling() {
- final int quality = 0;
+ final int credType = CREDENTIAL_TYPE_PASSWORD;
final int length = 1;
final int letters = 2;
final int upperCase = 3;
@@ -73,20 +65,21 @@
final int numeric = 5;
final int symbols = 6;
final int nonLetter = 7;
+ final int nonNumeric = 8;
+ final int seqLength = 9;
final Parcel parcel = Parcel.obtain();
- final PasswordMetrics metrics;
+ PasswordMetrics metrics = new PasswordMetrics(credType, length, letters, upperCase,
+ lowerCase, numeric, symbols, nonLetter, nonNumeric, seqLength);
try {
- new PasswordMetrics(
- quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter)
- .writeToParcel(parcel, 0);
+ metrics.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
metrics = PasswordMetrics.CREATOR.createFromParcel(parcel);
} finally {
parcel.recycle();
}
- assertEquals(quality, metrics.quality);
+ assertEquals(credType, metrics.credType);
assertEquals(length, metrics.length);
assertEquals(letters, metrics.letters);
assertEquals(upperCase, metrics.upperCase);
@@ -94,7 +87,8 @@
assertEquals(numeric, metrics.numeric);
assertEquals(symbols, metrics.symbols);
assertEquals(nonLetter, metrics.nonLetter);
-
+ assertEquals(nonNumeric, metrics.nonNumeric);
+ assertEquals(seqLength, metrics.seqLength);
}
@Test
@@ -111,23 +105,6 @@
}
@Test
- public void testComputeForPassword_quality() {
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
- PasswordMetrics.computeForPassword("a1".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- PasswordMetrics.computeForPassword("a".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- PasswordMetrics.computeForPassword("*~&%$".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
- PasswordMetrics.computeForPassword("1".getBytes()).quality);
- // contains a long sequence so isn't complex
- assertEquals(PASSWORD_QUALITY_NUMERIC,
- PasswordMetrics.computeForPassword("1234".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- PasswordMetrics.computeForPassword("".getBytes()).quality);
- }
-
- @Test
public void testMaxLengthSequence() {
assertEquals(4, PasswordMetrics.maxLengthSequence("1234".getBytes()));
assertEquals(5, PasswordMetrics.maxLengthSequence("13579".getBytes()));
@@ -142,69 +119,15 @@
}
@Test
- public void testEquals() {
- PasswordMetrics metrics0 = new PasswordMetrics();
- PasswordMetrics metrics1 = new PasswordMetrics();
- assertNotEquals(metrics0, null);
- assertNotEquals(metrics0, new Object());
- assertEquals(metrics0, metrics0);
- assertEquals(metrics0, metrics1);
-
- assertEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4));
-
- assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
-
- assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
-
- metrics0 = PasswordMetrics.computeForPassword("1234abcd,./".getBytes());
- metrics1 = PasswordMetrics.computeForPassword("1234abcd,./".getBytes());
- assertEquals(metrics0, metrics1);
- metrics1.letters++;
- assertNotEquals(metrics0, metrics1);
- metrics1.letters--;
- metrics1.upperCase++;
- assertNotEquals(metrics0, metrics1);
- metrics1.upperCase--;
- metrics1.lowerCase++;
- assertNotEquals(metrics0, metrics1);
- metrics1.lowerCase--;
- metrics1.numeric++;
- assertNotEquals(metrics0, metrics1);
- metrics1.numeric--;
- metrics1.symbols++;
- assertNotEquals(metrics0, metrics1);
- metrics1.symbols--;
- metrics1.nonLetter++;
- assertNotEquals(metrics0, metrics1);
- metrics1.nonLetter--;
- assertEquals(metrics0, metrics1);
-
-
- }
-
- @Test
- public void testConstructQuality() {
- PasswordMetrics expected = new PasswordMetrics();
- expected.quality = PASSWORD_QUALITY_COMPLEX;
-
- PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
-
- assertEquals(expected, actual);
- }
-
- @Test
public void testDetermineComplexity_none() {
assertEquals(PASSWORD_COMPLEXITY_NONE,
- PasswordMetrics.computeForPassword("".getBytes()).determineComplexity());
+ new PasswordMetrics(CREDENTIAL_TYPE_NONE).determineComplexity());
}
@Test
public void testDetermineComplexity_lowSomething() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity());
+ new PasswordMetrics(CREDENTIAL_TYPE_PATTERN).determineComplexity());
}
@Test
@@ -324,122 +247,84 @@
}
@Test
- public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
- PasswordMetrics metrics =
- getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
-
- assertTrue(metrics.isDefault());
+ public void testMerge_single() {
+ PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Collections.singletonList(metrics)).credType);
}
@Test
- public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
- PasswordMetrics metrics =
- getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
+ public void testMerge_credentialTypes() {
+ PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ assertEquals(CREDENTIAL_TYPE_PATTERN,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, pattern}))
+ .credType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, password}))
+ .credType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{password, pattern}))
+ .credType);
}
@Test
- public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ public void testValidatePasswordMetrics_credentialTypes() {
+ PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 8, metrics.length);
+ // To pass minimal length check.
+ password.length = 4;
+
+ // No errors expected, credential is of stronger or equal type.
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, none));
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, pattern));
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, password));
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, pattern));
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, password));
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, password));
+
+ // Now actual credential type is weaker than required:
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, none),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, none),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, pattern),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
}
- @Test
- public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+ /**
+ * @param expected sequense of validation error codes followed by requirement values, must have
+ * even number of elements. Empty means no errors.
+ */
+ private void assertValidationErrors(
+ List<PasswordValidationError> actualErrors, int... expected) {
+ assertEquals("Test programming error: content shoud have even number of elements",
+ 0, expected.length % 2);
+ assertEquals("wrong number of validation errors", expected.length / 2, actualErrors.size());
+ HashMap<Integer, Integer> errorMap = new HashMap<>();
+ for (PasswordValidationError error : actualErrors) {
+ errorMap.put(error.errorCode, error.requirement);
+ }
- assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
- }
-
- @Test
- public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
- }
-
- @Test
- public void testGetActualRequiredQuality_nonComplex() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_NUMERIC_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNone() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNumeric() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ true,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresLetters() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ true);
-
- assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ true,
- /* requiresLettersOrSymbols= */ true);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
- }
-
- @Test
- public void testGetMinimumMetrics_userInputStricter() {
- PasswordMetrics metrics = getMinimumMetrics(
- PASSWORD_COMPLEXITY_HIGH,
- PASSWORD_QUALITY_ALPHANUMERIC,
- PASSWORD_QUALITY_NUMERIC,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
- assertEquals(/* expected= */ 6, metrics.length);
- }
-
- @Test
- public void testGetMinimumMetrics_actualRequiredQualityStricter() {
- PasswordMetrics metrics = getMinimumMetrics(
- PASSWORD_COMPLEXITY_HIGH,
- PASSWORD_QUALITY_UNSPECIFIED,
- PASSWORD_QUALITY_NUMERIC,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 8, metrics.length);
+ for (int i = 0; i < expected.length / 2; i++) {
+ final int expectedError = expected[i * 2];
+ final int expectedRequirement = expected[i * 2 + 1];
+ assertTrue("error expected but not reported: " + expectedError,
+ errorMap.containsKey(expectedError));
+ assertEquals("unexpected requirement for error: " + expectedError,
+ Integer.valueOf(expectedRequirement), errorMap.get(expectedError));
+ }
}
}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
new file mode 100644
index 0000000..e951054
--- /dev/null
+++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PasswordPolicyTest {
+
+ public static final int TEST_VALUE = 10;
+
+ @Test
+ public void testGetMinMetrics_unspecified() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_UNSPECIFIED);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_NONE, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_something() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_SOMETHING);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PATTERN, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_biometricWeak() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_BIOMETRIC_WEAK);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PATTERN, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_numeric() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits.
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_numericDefaultLength() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
+ policy.length = 0; // reset to default
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(0, minMetrics.length);
+ }
+
+ @Test
+ public void testGetMinMetrics_numericComplex() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(PasswordMetrics.MAX_ALLOWED_SEQUENCE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_alphabetic() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_ALPHABETIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(1, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_alphanumeric() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_ALPHANUMERIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(1, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(1, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_complex() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_COMPLEX);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(TEST_VALUE, minMetrics.letters);
+ assertEquals(TEST_VALUE, minMetrics.lowerCase);
+ assertEquals(TEST_VALUE, minMetrics.upperCase);
+ assertEquals(TEST_VALUE, minMetrics.symbols);
+ assertEquals(TEST_VALUE, minMetrics.numeric);
+ assertEquals(TEST_VALUE, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_complexDefault() {
+ PasswordPolicy policy = new PasswordPolicy();
+ policy.quality = PASSWORD_QUALITY_COMPLEX;
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(1, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(1, minMetrics.symbols);
+ assertEquals(1, minMetrics.numeric);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ private PasswordPolicy testPolicy(int quality) {
+ PasswordPolicy result = new PasswordPolicy();
+ result.quality = quality;
+ result.length = TEST_VALUE;
+ result.letters = TEST_VALUE;
+ result.lowerCase = TEST_VALUE;
+ result.upperCase = TEST_VALUE;
+ result.numeric = TEST_VALUE;
+ result.symbols = TEST_VALUE;
+ result.nonLetter = TEST_VALUE;
+ return result;
+ }
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 4471017..d900a42 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1359,9 +1359,44 @@
* Specifies the known formats a bitmap can be compressed into
*/
public enum CompressFormat {
- JPEG (0),
- PNG (1),
- WEBP (2);
+ /**
+ * Compress to the JPEG format. {@code quality} of {@code 0} means
+ * compress for the smallest size. {@code 100} means compress for max
+ * visual quality.
+ */
+ JPEG (0),
+ /**
+ * Compress to the PNG format. PNG is lossless, so {@code quality} is
+ * ignored.
+ */
+ PNG (1),
+ /**
+ * Compress to the WEBP format. {@code quality} of {@code 0} means
+ * compress for the smallest size. {@code 100} means compress for max
+ * visual quality. As of {@link android.os.Build.VERSION_CODES#Q}, a
+ * value of {@code 100} results in a file in the lossless WEBP format.
+ * Otherwise the file will be in the lossy WEBP format.
+ *
+ * @deprecated in favor of the more explicit
+ * {@link CompressFormat#WEBP_LOSSY} and
+ * {@link CompressFormat#WEBP_LOSSLESS}.
+ */
+ @Deprecated
+ WEBP (2),
+ /**
+ * Compress to the WEBP lossy format. {@code quality} of {@code 0} means
+ * compress for the smallest size. {@code 100} means compress for max
+ * visual quality.
+ */
+ WEBP_LOSSY (3),
+ /**
+ * Compress to the WEBP lossless format. {@code quality} refers to how
+ * much effort to put into compression. A value of {@code 0} means to
+ * compress quickly, resulting in a relatively large file size.
+ * {@code 100} means to spend more time compressing, resulting in a
+ * smaller file.
+ */
+ WEBP_LOSSLESS (4);
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
@@ -1385,10 +1420,8 @@
* pixels).
*
* @param format The format of the compressed image
- * @param quality Hint to the compressor, 0-100. 0 meaning compress for
- * small size, 100 meaning compress for max quality. Some
- * formats, like PNG which is lossless, will ignore the
- * quality setting
+ * @param quality Hint to the compressor, 0-100. The value is interpreted
+ * differently depending on the {@link CompressFormat}.
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 9c4b5e8..06d4fbd 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1380,9 +1380,9 @@
*/
@NonNull
static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
- if (index < 0 || index >= Named.values().length) {
+ if (index < 0 || index >= sNamedColorSpaces.length) {
throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
- Named.values().length + ")");
+ sNamedColorSpaces.length + ")");
}
return sNamedColorSpaces[index];
}
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 6824be8..27274d1 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -1211,7 +1211,8 @@
}
/**
- * Attaches an extra {@link Location} to this Location.
+ * Attaches an extra {@link Location} to this Location. This is useful for location providers
+ * to set the {@link #EXTRA_NO_GPS_LOCATION} extra to provide coarse locations for clients.
*
* @param key the key associated with the Location extra
* @param value the Location to attach
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 0b3e1c3..70bfb54 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1971,7 +1971,7 @@
public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {}
/**
- * Registers a GPS Measurement callback.
+ * Registers a GPS Measurement callback which will run on a binder threadS.
*
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
@@ -1983,7 +1983,7 @@
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
- return registerGnssMeasurementsCallback(callback, null);
+ return registerGnssMeasurementsCallback(Runnable::run, callback);
}
/**
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 7ed431d..cc5ddeb 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -224,7 +224,7 @@
* @return The meta data value associate with the given keyCode on success;
* null on failure.
*/
- public native String extractMetadata(int keyCode);
+ public native @Nullable String extractMetadata(int keyCode);
/**
* This method is similar to {@link #getFrameAtTime(long, int, BitmapParams)}
@@ -255,7 +255,7 @@
*
* @see {@link #getFrameAtTime(long, int, BitmapParams)}
*/
- public Bitmap getFrameAtTime(long timeUs, @Option int option) {
+ public @Nullable Bitmap getFrameAtTime(long timeUs, @Option int option) {
if (option < OPTION_PREVIOUS_SYNC ||
option > OPTION_CLOSEST) {
throw new IllegalArgumentException("Unsupported option: " + option);
@@ -301,7 +301,7 @@
*
* @see {@link #getFrameAtTime(long, int)}
*/
- public Bitmap getFrameAtTime(
+ public @Nullable Bitmap getFrameAtTime(
long timeUs, @Option int option, @NonNull BitmapParams params) {
if (option < OPTION_PREVIOUS_SYNC ||
option > OPTION_CLOSEST) {
@@ -343,7 +343,7 @@
* is less than or equal to 0.
* @see {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)}
*/
- public Bitmap getScaledFrameAtTime(
+ public @Nullable Bitmap getScaledFrameAtTime(
long timeUs, @Option int option, int dstWidth, int dstHeight) {
validate(option, dstWidth, dstHeight);
return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null);
@@ -388,7 +388,7 @@
* is less than or equal to 0.
* @see {@link #getScaledFrameAtTime(long, int, int, int)}
*/
- public Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
+ public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
int dstWidth, int dstHeight, @NonNull BitmapParams params) {
validate(option, dstWidth, dstHeight);
return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, params);
@@ -430,7 +430,7 @@
*
* @see #getFrameAtTime(long, int)
*/
- public Bitmap getFrameAtTime(long timeUs) {
+ public @Nullable Bitmap getFrameAtTime(long timeUs) {
return getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC);
}
@@ -452,7 +452,7 @@
* @see #getFrameAtTime(long)
* @see #getFrameAtTime(long, int)
*/
- public Bitmap getFrameAtTime() {
+ public @Nullable Bitmap getFrameAtTime() {
return _getFrameAtTime(
-1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/, null);
}
@@ -528,7 +528,7 @@
* @see #getFramesAtIndex(int, int, BitmapParams)
* @see #getFramesAtIndex(int, int)
*/
- public Bitmap getFrameAtIndex(int frameIndex, @NonNull BitmapParams params) {
+ public @Nullable Bitmap getFrameAtIndex(int frameIndex, @NonNull BitmapParams params) {
List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1, params);
return bitmaps.get(0);
}
@@ -550,7 +550,7 @@
* @see #getFramesAtIndex(int, int, BitmapParams)
* @see #getFramesAtIndex(int, int)
*/
- public Bitmap getFrameAtIndex(int frameIndex) {
+ public @Nullable Bitmap getFrameAtIndex(int frameIndex) {
List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1);
return bitmaps.get(0);
}
@@ -653,7 +653,7 @@
* @see #getPrimaryImage(BitmapParams)
* @see #getPrimaryImage()
*/
- public Bitmap getImageAtIndex(int imageIndex, @NonNull BitmapParams params) {
+ public @Nullable Bitmap getImageAtIndex(int imageIndex, @NonNull BitmapParams params) {
return getImageAtIndexInternal(imageIndex, params);
}
@@ -691,7 +691,7 @@
* @see #getPrimaryImage(BitmapParams)
* @see #getPrimaryImage()
*/
- public Bitmap getImageAtIndex(int imageIndex) {
+ public @Nullable Bitmap getImageAtIndex(int imageIndex) {
return getImageAtIndexInternal(imageIndex, null);
}
@@ -713,7 +713,7 @@
* @see #getImageAtIndex(int)
* @see #getPrimaryImage()
*/
- public Bitmap getPrimaryImage(@NonNull BitmapParams params) {
+ public @Nullable Bitmap getPrimaryImage(@NonNull BitmapParams params) {
return getImageAtIndexInternal(-1, params);
}
@@ -729,7 +729,7 @@
* @see #getImageAtIndex(int)
* @see #getPrimaryImage(BitmapParams)
*/
- public Bitmap getPrimaryImage() {
+ public @Nullable Bitmap getPrimaryImage() {
return getImageAtIndexInternal(-1, null);
}
@@ -755,7 +755,7 @@
*
* @return null if no such graphic is found.
*/
- public byte[] getEmbeddedPicture() {
+ public @Nullable byte[] getEmbeddedPicture() {
return getEmbeddedPicture(EMBEDDED_PICTURE_TYPE_ANY);
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index abd774d..59bd96f 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -79,6 +79,8 @@
@Nullable
final Bundle mExtras;
+ private final String mUniqueId;
+
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
mProviderId = builder.mProviderId;
@@ -90,6 +92,7 @@
mVolumeMax = builder.mVolumeMax;
mVolumeHandling = builder.mVolumeHandling;
mExtras = builder.mExtras;
+ mUniqueId = createUniqueId();
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -103,6 +106,15 @@
mVolumeMax = in.readInt();
mVolumeHandling = in.readInt();
mExtras = in.readBundle();
+ mUniqueId = createUniqueId();
+ }
+
+ private String createUniqueId() {
+ String uniqueId = null;
+ if (mProviderId != null) {
+ uniqueId = mProviderId + ":" + mId;
+ }
+ return uniqueId;
}
/**
@@ -147,13 +159,33 @@
return Objects.hash(mId, mName, mDescription, mSupportedCategories);
}
+ /**
+ * Gets the id of the route.
+ * Use {@link #getUniqueId()} if you need a unique identifier.
+ *
+ * @see #getUniqueId()
+ */
@NonNull
public String getId() {
return mId;
}
/**
- * Gets the provider id of the route.
+ * Gets the unique id of the route. A route obtained from
+ * {@link com.android.server.media.MediaRouterService} always has a unique id.
+ *
+ * @return unique id of the route or null if it has no unique id.
+ */
+ @Nullable
+ public String getUniqueId() {
+ return mUniqueId;
+ }
+
+ /**
+ * Gets the provider id of the route. It is assigned automatically by
+ * {@link com.android.server.media.MediaRouterService}.
+ *
+ * @return provider id of the route or null if it's not set.
* @hide
*/
@Nullable
@@ -337,7 +369,7 @@
@NonNull
public Builder setProviderId(@NonNull String providerId) {
if (TextUtils.isEmpty(providerId)) {
- throw new IllegalArgumentException("id must not be null or empty");
+ throw new IllegalArgumentException("providerId must not be null or empty");
}
mProviderId = providerId;
return this;
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 58deff2..5f5d200 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -30,7 +30,7 @@
* @hide
*/
public abstract class MediaRoute2ProviderService extends Service {
- private static final String TAG = "MediaRouteProviderSrv";
+ private static final String TAG = "MR2ProviderService";
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index aca40d8..b52e2d6 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -44,7 +44,7 @@
* @hide
*/
public class MediaRouter2 {
- private static final String TAG = "MediaRouter";
+ private static final String TAG = "MR2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sLock = new Object();
@@ -54,7 +54,8 @@
private Context mContext;
private final IMediaRouterService mMediaRouterService;
- private CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+ new CopyOnWriteArrayList<>();
@GuardedBy("sLock")
private List<String> mControlCategories = Collections.emptyList();
@GuardedBy("sLock")
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 4f2a295..0d7b6ff 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -46,7 +46,7 @@
* @hide
*/
public class MediaRouter2Manager {
- private static final String TAG = "MediaRouter2Manager";
+ private static final String TAG = "MR2Manager";
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 680c879..f4f8d0b 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -26,7 +26,7 @@
import java.util.Map;
public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService {
- private static final String TAG = "SampleMediaRoute2Serv";
+ private static final String TAG = "SampleMR2ProviderSvc";
public static final String ROUTE_ID1 = "route_id1";
public static final String ROUTE_NAME1 = "Sample Route 1";
diff --git a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
index 72ec8d8..94f5b96 100644
--- a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
@@ -48,6 +48,18 @@
/>
<com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/grid"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ systemui:intent="intent:#Intent;component=com.android.car.home/.AppGridActivity;end"
+ systemui:longIntent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ android:src="@drawable/car_ic_apps"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingTop="30dp"
+ android:paddingBottom="30dp"
+ />
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -58,6 +70,7 @@
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
+
</LinearLayout>
<LinearLayout
@@ -78,6 +91,7 @@
android:alpha="0.7"
/>
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 897976f..18ae582 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -22,6 +22,8 @@
android:layout_height="match_parent"
android:background="@drawable/system_bar_background"
android:orientation="vertical">
+ <!--The 20dp padding is the difference between the background selected icon size and the ripple
+ that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
<LinearLayout
android:id="@id/nav_buttons"
android:layout_width="match_parent"
@@ -37,7 +39,6 @@
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
- systemui:longIntent="intent:#Intent;action=com.google.android.demandspace.START;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:useMoreIcon="false"
/>
@@ -108,13 +109,11 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <!-- Click handling will be initialized in CarNavigationBarView because its
- id = notifications which is treated special for the opening of the notification panel
- -->
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/notifications"
style="@style/NavigationBarButton"
- android:src="@drawable/car_ic_notification"
+ systemui:icon="@drawable/car_ic_notification"
+ systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
systemui:selectedIcon="@drawable/car_ic_notification_selected"
systemui:useMoreIcon="false"
/>
@@ -124,13 +123,13 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.statusbar.car.CarFacetButton
+ <com.android.systemui.statusbar.car.AssitantButton
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
- systemui:intent="intent:#Intent;action=com.google.android.demandspace.START;end"
systemui:useMoreIcon="false"
/>
+
</LinearLayout>
<LinearLayout
@@ -138,10 +137,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:paddingStart="@*android:dimen/car_keyline_1"
- android:paddingEnd="@*android:dimen/car_keyline_1"
+ android:paddingStart="@dimen/car_keyline_1"
+ android:paddingEnd="@dimen/car_keyline_1"
android:gravity="center"
android:visibility="gone">
+
</LinearLayout>
-</com.android.systemui.statusbar.car.CarNavigationBarView>
+</com.android.systemui.statusbar.car.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
index 72ec8d8..d36d1d6 100644
--- a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
@@ -25,6 +25,9 @@
android:orientation="vertical"
android:background="@drawable/system_bar_background">
+ <!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed
+ rotation so skip this level of the hierarchy.
+ -->
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
@@ -48,6 +51,18 @@
/>
<com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/grid"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ systemui:intent="intent:#Intent;component=com.android.car.home/.AppGridActivity;launchFlags=0x14000000;end"
+ systemui:longIntent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ android:src="@drawable/car_ic_apps"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingTop="30dp"
+ android:paddingBottom="30dp"
+ />
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -58,6 +73,7 @@
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
+
</LinearLayout>
<LinearLayout
@@ -78,6 +94,7 @@
android:alpha="0.7"
/>
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 90aba2f..1eeaa7c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -87,6 +87,7 @@
import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationListener;
@@ -102,7 +103,7 @@
import com.android.systemui.statusbar.car.hvac.TemperatureView;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.NewNotifPipeline;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
@@ -140,6 +141,8 @@
import javax.inject.Inject;
import javax.inject.Named;
+import dagger.Lazy;
+
/**
* A status bar (and navigation bar) tailored for the automotive use case.
*/
@@ -252,6 +255,7 @@
@Inject
public CarStatusBar(
Context context,
+ FeatureFlags featureFlags,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -266,7 +270,7 @@
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- NotifPipelineInitializer notifPipelineInitializer,
+ Lazy<NewNotifPipeline> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -309,6 +313,7 @@
DozeParameters dozeParameters) {
super(
context,
+ featureFlags,
lightBarController,
autoHideController,
keyguardUpdateMonitor,
@@ -323,7 +328,7 @@
dynamicPrivacyController,
bypassHeadsUpNotifier,
allowNotificationLongPress,
- notifPipelineInitializer,
+ newNotifPipeline,
falsingManager,
broadcastDispatcher,
remoteInputQuickSettingsDisabler,
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index a18600a..2b84196 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -48,11 +48,21 @@
return getEnabledServicesFromSettings(context, UserHandle.myUserId());
}
+ /**
+ * Check if the accessibility service is crashed
+ *
+ * @param packageName The package name to check
+ * @param serviceName The service name to check
+ * @param installedServiceInfos The list of installed accessibility service
+ * @return {@code true} if the accessibility service is crashed for the user.
+ * {@code false} otherwise.
+ */
public static boolean hasServiceCrashed(String packageName, String serviceName,
- List<AccessibilityServiceInfo> enabledServiceInfos) {
- for (int i = 0; i < enabledServiceInfos.size(); i++) {
- AccessibilityServiceInfo accessibilityServiceInfo = enabledServiceInfos.get(i);
- final ServiceInfo serviceInfo = enabledServiceInfos.get(i).getResolveInfo().serviceInfo;
+ List<AccessibilityServiceInfo> installedServiceInfos) {
+ for (int i = 0; i < installedServiceInfos.size(); i++) {
+ final AccessibilityServiceInfo accessibilityServiceInfo = installedServiceInfos.get(i);
+ final ServiceInfo serviceInfo =
+ installedServiceInfos.get(i).getResolveInfo().serviceInfo;
if (TextUtils.equals(serviceInfo.packageName, packageName)
&& TextUtils.equals(serviceInfo.name, serviceName)) {
return accessibilityServiceInfo.crashed;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index a9fe54b..4d061e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -49,6 +49,12 @@
private static final String TAG = "WorkLockActivity";
/**
+ * Add additional extra {@link com.android.settings.password.ConfirmDeviceCredentialActivity} to
+ * enable device policy management enforcement from systemui.
+ */
+ public static final String EXTRA_FROM_WORK_LOCK_ACTIVITY = "from_work_lock_activity";
+
+ /**
* Contains a {@link TaskDescription} for the activity being covered.
*/
static final String EXTRA_TASK_DESCRIPTION =
@@ -151,6 +157,7 @@
if (target != null) {
credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+ credential.putExtra(EXTRA_FROM_WORK_LOCK_ACTIVITY, true);
}
startActivityForResult(credential, REQUEST_CODE_CONFIRM_CREDENTIALS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
new file mode 100644
index 0000000..f91341f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Class to manage simple DeviceConfig-based feature flags.
+ *
+ * To enable or disable a flag, run:
+ *
+ * {@code
+ * $ adb shell device_config put systemui <key> <true|false>
+* }
+ *
+ * You will probably need to @{$ adb reboot} afterwards in order for the code to pick up the change.
+ */
+public class FeatureFlags {
+ private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
+
+ @Inject
+ public FeatureFlags() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ "systemui",
+ new HandlerExecutor(new Handler(Looper.getMainLooper())),
+ this::onPropertiesChanged);
+ }
+
+ public boolean isNewNotifPipelineEnabled() {
+ return getDeviceConfigFlag("notification.newpipeline.enabled", false);
+ }
+
+ private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ synchronized (mCachedDeviceConfigFlags) {
+ for (String key : properties.getKeyset()) {
+ mCachedDeviceConfigFlags.remove(key);
+ }
+ }
+ }
+
+ private boolean getDeviceConfigFlag(String key, boolean defaultValue) {
+ synchronized (mCachedDeviceConfigFlags) {
+ Boolean flag = mCachedDeviceConfigFlags.get(key);
+ if (flag == null) {
+ flag = DeviceConfig.getBoolean("systemui", key, defaultValue);
+ mCachedDeviceConfigFlags.put(key, flag);
+ }
+ return flag;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java
new file mode 100644
index 0000000..31921a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Log;
+
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Initialization code for the new notification pipeline.
+ */
+@Singleton
+public class NewNotifPipeline {
+ private final NotifCollection mNotifCollection;
+
+ @Inject
+ public NewNotifPipeline(
+ NotifCollection notifCollection) {
+ mNotifCollection = notifCollection;
+ }
+
+ /** Hooks the new pipeline up to NotificationManager */
+ public void initialize(
+ NotificationListener notificationService) {
+ mNotifCollection.attach(notificationService);
+
+ Log.d(TAG, "Notif pipeline initialized");
+ }
+
+ private static final String TAG = "NewNotifPipeline";
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
deleted file mode 100644
index df70828..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.statusbar.NotificationListener;
-
-import javax.inject.Inject;
-
-/**
- * Initialization code for the new notification pipeline.
- */
-public class NotifPipelineInitializer {
-
- @Inject
- public NotifPipelineInitializer() {
- }
-
- public void initialize(
- NotificationListener notificationService) {
-
- // TODO Put real code here
- notificationService.setDownstreamListener(new NotificationListener.NotifServiceListener() {
- @Override
- public void onNotificationPosted(StatusBarNotification sbn,
- NotificationListenerService.RankingMap rankingMap) {
- Log.d(TAG, "onNotificationPosted " + sbn.getKey());
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- NotificationListenerService.RankingMap rankingMap) {
- Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- NotificationListenerService.RankingMap rankingMap, int reason) {
- Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
- }
-
- @Override
- public void onNotificationRankingUpdate(
- NotificationListenerService.RankingMap rankingMap) {
- Log.d(TAG, "onNotificationRankingUpdate");
- }
- });
- }
-
- private static final String TAG = "NotifInitializer";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
new file mode 100644
index 0000000..ecce6ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import android.service.notification.NotificationStats.DismissalSentiment;
+import android.service.notification.NotificationStats.DismissalSurface;
+
+import com.android.internal.statusbar.NotificationVisibility;
+
+/** Information that must be supplied when dismissing a notification on the behalf of the user. */
+public class DismissedByUserStats {
+ public final @DismissalSurface int dismissalSurface;
+ public final @DismissalSentiment int dismissalSentiment;
+ public final NotificationVisibility notificationVisibility;
+
+ public DismissedByUserStats(
+ @DismissalSurface int dismissalSurface,
+ @DismissalSentiment int dismissalSentiment,
+ NotificationVisibility notificationVisibility) {
+ this.dismissalSurface = dismissalSurface;
+ this.dismissalSentiment = dismissalSentiment;
+ this.notificationVisibility = notificationVisibility;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
new file mode 100644
index 0000000..3203c30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_ERROR;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_OPTIMIZATION;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_CHANGED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
+import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
+import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
+import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
+import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
+import com.android.systemui.util.Assert;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Keeps a record of all of the "active" notifications, i.e. the notifications that are currently
+ * posted to the phone. This collection is unsorted, ungrouped, and unfiltered. Just because a
+ * notification appears in this collection doesn't mean that it's currently present in the shade
+ * (notifications can be hidden for a variety of reasons). Code that cares about what notifications
+ * are *visible* right now should register listeners later in the pipeline.
+ *
+ * Each notification is represented by a {@link NotificationEntry}, which is itself made up of two
+ * parts: a {@link StatusBarNotification} and a {@link Ranking}. When notifications are updated,
+ * their underlying SBNs and Rankings are swapped out, but the enclosing NotificationEntry (and its
+ * associated key) remain the same. In general, an SBN can only be updated when the notification is
+ * reposted by the source app; Rankings are updated much more often, usually every time there is an
+ * update from any kind from NotificationManager.
+ *
+ * In general, this collection closely mirrors the list maintained by NotificationManager, but it
+ * can occasionally diverge due to lifetime extenders (see
+ * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}).
+ *
+ * Interested parties can register listeners
+ * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are
+ * added, updated, or removed.
+ */
+@MainThread
+@Singleton
+public class NotifCollection {
+ private final IStatusBarService mStatusBarService;
+
+ private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
+ private final Collection<NotificationEntry> mReadOnlyNotificationSet =
+ Collections.unmodifiableCollection(mNotificationSet.values());
+
+ @Nullable private NotifListBuilder mListBuilder;
+ private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
+ private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
+
+ private boolean mAttached = false;
+ private boolean mAmDispatchingToOtherCode;
+
+ @Inject
+ public NotifCollection(IStatusBarService statusBarService) {
+ Assert.isMainThread();
+ mStatusBarService = statusBarService;
+ }
+
+ /** Initializes the NotifCollection and registers it to receive notification events. */
+ public void attach(NotificationListener listenerService) {
+ Assert.isMainThread();
+ if (mAttached) {
+ throw new RuntimeException("attach() called twice");
+ }
+ mAttached = true;
+
+ listenerService.setDownstreamListener(mNotifServiceListener);
+ }
+
+ /**
+ * Sets the class responsible for converting the collection into the list of currently-visible
+ * notifications.
+ */
+ public void setListBuilder(NotifListBuilder listBuilder) {
+ Assert.isMainThread();
+ mListBuilder = listBuilder;
+ }
+
+ /**
+ * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+ * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+ * but it can diverge slightly due to lifetime extenders.
+ *
+ * The returned list is read-only, unsorted, unfiltered, and ungrouped.
+ */
+ public Collection<NotificationEntry> getNotifs() {
+ Assert.isMainThread();
+ return mReadOnlyNotificationSet;
+ }
+
+ /**
+ * Registers a listener to be informed when notifications are added, removed or updated.
+ */
+ public void addCollectionListener(NotifCollectionListener listener) {
+ Assert.isMainThread();
+ mNotifCollectionListeners.add(listener);
+ }
+
+ /**
+ * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
+ * dismissed or retracted to be temporarily retained in the collection.
+ */
+ public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+ Assert.isMainThread();
+ checkForReentrantCall();
+ if (mLifetimeExtenders.contains(extender)) {
+ throw new IllegalArgumentException("Extender " + extender + " already added.");
+ }
+ mLifetimeExtenders.add(extender);
+ extender.setCallback(this::onEndLifetimeExtension);
+ }
+
+ /**
+ * Dismiss a notification on behalf of the user.
+ */
+ public void dismissNotification(
+ NotificationEntry entry,
+ @CancellationReason int reason,
+ @NonNull DismissedByUserStats stats) {
+ Assert.isMainThread();
+ checkNotNull(stats);
+ checkForReentrantCall();
+
+ removeNotification(entry.key(), null, reason, stats);
+ }
+
+ private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ Assert.isMainThread();
+
+ NotificationEntry entry = mNotificationSet.get(sbn.getKey());
+
+ if (entry == null) {
+ // A new notification!
+ Log.d(TAG, "POSTED " + sbn.getKey());
+
+ entry = new NotificationEntry(sbn, requireRanking(rankingMap, sbn.getKey()));
+ mNotificationSet.put(sbn.getKey(), entry);
+ applyRanking(rankingMap);
+
+ dispatchOnEntryAdded(entry);
+
+ } else {
+ // Update to an existing entry
+ Log.d(TAG, "UPDATED " + sbn.getKey());
+
+ // Notification is updated so it is essentially re-added and thus alive again. Don't
+ // need to keep its lifetime extended.
+ cancelLifetimeExtension(entry);
+
+ entry.setNotification(sbn);
+ applyRanking(rankingMap);
+
+ dispatchOnEntryUpdated(entry);
+ }
+
+ rebuildList();
+ }
+
+ private void onNotificationRemoved(
+ StatusBarNotification sbn,
+ @Nullable RankingMap rankingMap,
+ int reason) {
+ Assert.isMainThread();
+ Log.d(TAG, "REMOVED " + sbn.getKey() + " reason=" + reason);
+ removeNotification(sbn.getKey(), rankingMap, reason, null);
+ }
+
+ private void onNotificationRankingUpdate(RankingMap rankingMap) {
+ Assert.isMainThread();
+ applyRanking(rankingMap);
+ rebuildList();
+ }
+
+ private void removeNotification(
+ String key,
+ @Nullable RankingMap rankingMap,
+ @CancellationReason int reason,
+ DismissedByUserStats dismissedByUserStats) {
+
+ NotificationEntry entry = mNotificationSet.get(key);
+ if (entry == null) {
+ throw new IllegalStateException("No notification to remove with key " + key);
+ }
+
+ entry.mLifetimeExtenders.clear();
+ mAmDispatchingToOtherCode = true;
+ for (NotifLifetimeExtender extender : mLifetimeExtenders) {
+ if (extender.shouldExtendLifetime(entry, reason)) {
+ entry.mLifetimeExtenders.add(extender);
+ }
+ }
+ mAmDispatchingToOtherCode = false;
+
+ if (!isLifetimeExtended(entry)) {
+ mNotificationSet.remove(entry.key());
+
+ if (dismissedByUserStats != null) {
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.sbn().getPackageName(),
+ entry.sbn().getTag(),
+ entry.sbn().getId(),
+ entry.sbn().getUser().getIdentifier(),
+ entry.sbn().getKey(),
+ dismissedByUserStats.dismissalSurface,
+ dismissedByUserStats.dismissalSentiment,
+ dismissedByUserStats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+ }
+
+ if (rankingMap != null) {
+ applyRanking(rankingMap);
+ }
+
+ dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */);
+ }
+
+ rebuildList();
+ }
+
+ private void applyRanking(RankingMap rankingMap) {
+ for (NotificationEntry entry : mNotificationSet.values()) {
+ if (!isLifetimeExtended(entry)) {
+ Ranking ranking = requireRanking(rankingMap, entry.key());
+ entry.setRanking(ranking);
+ }
+ }
+ }
+
+ private void rebuildList() {
+ if (mListBuilder != null) {
+ mListBuilder.onBuildList(mReadOnlyNotificationSet);
+ }
+ }
+
+ private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) {
+ Assert.isMainThread();
+ if (!mAttached) {
+ return;
+ }
+ checkForReentrantCall();
+
+ if (!entry.mLifetimeExtenders.remove(extender)) {
+ throw new IllegalStateException(
+ String.format(
+ "Cannot end lifetime extension for extender \"%s\" (%s)",
+ extender.getName(),
+ extender));
+ }
+
+ if (!isLifetimeExtended(entry)) {
+ // TODO: This doesn't need to be undefined -- we can set either EXTENDER_EXPIRED or
+ // save the original reason
+ removeNotification(entry.key(), null, REASON_UNKNOWN, null);
+ }
+ }
+
+ private void cancelLifetimeExtension(NotificationEntry entry) {
+ mAmDispatchingToOtherCode = true;
+ for (NotifLifetimeExtender extender : entry.mLifetimeExtenders) {
+ extender.cancelLifetimeExtension(entry);
+ }
+ mAmDispatchingToOtherCode = false;
+ entry.mLifetimeExtenders.clear();
+ }
+
+ private boolean isLifetimeExtended(NotificationEntry entry) {
+ return entry.mLifetimeExtenders.size() > 0;
+ }
+
+ private void checkForReentrantCall() {
+ if (mAmDispatchingToOtherCode) {
+ throw new IllegalStateException("Reentrant call detected");
+ }
+ }
+
+ private static Ranking requireRanking(RankingMap rankingMap, String key) {
+ // TODO: Modify RankingMap so that we don't have to make a copy here
+ Ranking ranking = new Ranking();
+ if (!rankingMap.getRanking(key, ranking)) {
+ throw new IllegalArgumentException("Ranking map doesn't contain key: " + key);
+ }
+ return ranking;
+ }
+
+ private void dispatchOnEntryAdded(NotificationEntry entry) {
+ mAmDispatchingToOtherCode = true;
+ if (mListBuilder != null) {
+ mListBuilder.onBeginDispatchToListeners();
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryAdded(entry);
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private void dispatchOnEntryUpdated(NotificationEntry entry) {
+ mAmDispatchingToOtherCode = true;
+ if (mListBuilder != null) {
+ mListBuilder.onBeginDispatchToListeners();
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryUpdated(entry);
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private void dispatchOnEntryRemoved(
+ NotificationEntry entry,
+ @CancellationReason int reason,
+ boolean removedByUser) {
+ mAmDispatchingToOtherCode = true;
+ if (mListBuilder != null) {
+ mListBuilder.onBeginDispatchToListeners();
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryRemoved(entry, reason, removedByUser);
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private final NotifServiceListener mNotifServiceListener = new NotifServiceListener() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ NotifCollection.this.onNotificationPosted(sbn, rankingMap);
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ NotifCollection.this.onNotificationRemoved(sbn, rankingMap, REASON_UNKNOWN);
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ int reason) {
+ NotifCollection.this.onNotificationRemoved(sbn, rankingMap, reason);
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ NotifCollection.this.onNotificationRankingUpdate(rankingMap);
+ }
+ };
+
+ private static final String TAG = "NotifCollection";
+
+ @IntDef(prefix = { "REASON_" }, value = {
+ REASON_UNKNOWN,
+ REASON_CLICK,
+ REASON_CANCEL_ALL,
+ REASON_ERROR,
+ REASON_PACKAGE_CHANGED,
+ REASON_USER_STOPPED,
+ REASON_PACKAGE_BANNED,
+ REASON_APP_CANCEL,
+ REASON_APP_CANCEL_ALL,
+ REASON_LISTENER_CANCEL,
+ REASON_LISTENER_CANCEL_ALL,
+ REASON_GROUP_SUMMARY_CANCELED,
+ REASON_GROUP_OPTIMIZATION,
+ REASON_PACKAGE_SUSPENDED,
+ REASON_PROFILE_TURNED_OFF,
+ REASON_UNAUTOBUNDLED,
+ REASON_CHANNEL_BANNED,
+ REASON_SNOOZED,
+ REASON_TIMEOUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CancellationReason {}
+
+ public static final int REASON_UNKNOWN = 0;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
new file mode 100644
index 0000000..032620e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+
+/**
+ * Listener interface for {@link NotifCollection}.
+ */
+public interface NotifCollectionListener {
+ /**
+ * Called whenever a notification with a new key is posted.
+ */
+ default void onEntryAdded(NotificationEntry entry) {
+ }
+
+ /**
+ * Called whenever a notification with the same key as an existing notification is posted. By
+ * the time this listener is called, the entry's SBN and Ranking will already have been updated.
+ */
+ default void onEntryUpdated(NotificationEntry entry) {
+ }
+
+ /**
+ * Called immediately after a notification has been removed from the collection.
+ */
+ default void onEntryRemoved(
+ NotificationEntry entry,
+ @CancellationReason int reason,
+ boolean removedByUser) {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
new file mode 100644
index 0000000..2c7b138
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+
+/**
+ * A way for other code to temporarily extend the lifetime of a notification after it has been
+ * retracted. See {@link NotifCollection#addNotificationLifetimeExtender(NotifLifetimeExtender)}.
+ */
+public interface NotifLifetimeExtender {
+ /** Name to associate with this extender (for the purposes of debugging) */
+ String getName();
+
+ /**
+ * Called on the extender immediately after it has been registered. The extender should hang on
+ * to this callback and execute it whenever it no longer needs to extend the lifetime of a
+ * notification.
+ */
+ void setCallback(OnEndLifetimeExtensionCallback callback);
+
+ /**
+ * Called by the NotifCollection whenever a notification has been retracted (by the app) or
+ * dismissed (by the user). If the extender returns true, it is considered to be extending the
+ * lifetime of that notification. Lifetime-extended notifications are kept around until all
+ * active extenders expire their extension by calling onEndLifetimeExtension(). This method is
+ * called on all lifetime extenders even if earlier ones return true (in other words, multiple
+ * lifetime extenders can be extending a notification at the same time).
+ */
+ boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason);
+
+ /**
+ * Called by the NotifCollection to inform a lifetime extender that its extension of a notif
+ * is no longer valid (usually because the notif has been reposted and so no longer needs
+ * lifetime extension). The extender should clean up any references it has to the notif in
+ * question.
+ */
+ void cancelLifetimeExtension(NotificationEntry entry);
+
+ /** Callback for notifying the NotifCollection that a lifetime extension has expired. */
+ interface OnEndLifetimeExtensionCallback {
+ void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java
new file mode 100644
index 0000000..17fef68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import java.util.Collection;
+
+/**
+ * Interface for the class responsible for converting a NotifCollection into the final sorted,
+ * filtered, and grouped list of currently visible notifications.
+ */
+public interface NotifListBuilder {
+ /**
+ * Called after the NotifCollection has received an update from NotificationManager but before
+ * it dispatches any change events to its listeners. This is to inform the list builder that
+ * the first stage of the pipeline has been triggered. After events have been dispatched,
+ * onBuildList() will be called.
+ *
+ * While onBuildList() is always called after this method is called, the converse is not always
+ * true: sometimes the NotifCollection applies an update that does not need to dispatch events,
+ * in which case this method will be skipped and onBuildList will be called directly.
+ */
+ void onBeginDispatchToListeners();
+
+ /**
+ * Called by the NotifCollection to indicate that something in the collection has changed and
+ * that the list builder should regenerate the list.
+ */
+ void onBuildList(Collection<NotificationEntry> entries);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index c3211e3..b12461a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -94,6 +94,20 @@
public StatusBarNotification notification;
private Ranking mRanking;
+
+ /*
+ * Bookkeeping members
+ */
+
+ /** List of lifetime extenders that are extending the lifetime of this notification. */
+ final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
+
+
+ /*
+ * Old members
+ * TODO: Remove every member beneath this line if possible
+ */
+
public boolean noisy;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c092f9b..8e70d082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -179,6 +179,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -198,7 +199,7 @@
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.NewNotifPipeline;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationClicker;
@@ -246,6 +247,7 @@
import javax.inject.Named;
import javax.inject.Singleton;
+import dagger.Lazy;
import dagger.Subcomponent;
@Singleton
@@ -370,6 +372,7 @@
private final Object mQueueLock = new Object();
+ private final FeatureFlags mFeatureFlags;
private final StatusBarIconController mIconController;
private final DozeLog mDozeLog;
private final InjectionInflationController mInjectionInflater;
@@ -381,7 +384,7 @@
private final DynamicPrivacyController mDynamicPrivacyController;
private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
private final boolean mAllowNotificationLongPress;
- private final NotifPipelineInitializer mNotifPipelineInitializer;
+ private final Lazy<NewNotifPipeline> mNewNotifPipeline;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
@@ -621,6 +624,7 @@
@Inject
public StatusBar(
Context context,
+ FeatureFlags featureFlags,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -635,7 +639,7 @@
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- NotifPipelineInitializer notifPipelineInitializer,
+ Lazy<NewNotifPipeline> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -677,6 +681,7 @@
NotifLog notifLog,
DozeParameters dozeParameters) {
super(context);
+ mFeatureFlags = featureFlags;
mLightBarController = lightBarController;
mAutoHideController = autoHideController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -691,7 +696,7 @@
mDynamicPrivacyController = dynamicPrivacyController;
mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
mAllowNotificationLongPress = allowNotificationLongPress;
- mNotifPipelineInitializer = notifPipelineInitializer;
+ mNewNotifPipeline = newNotifPipeline;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
@@ -1211,7 +1216,9 @@
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
- mNotifPipelineInitializer.initialize(mNotificationListener);
+ if (mFeatureFlags.isNewNotifPipelineEnabled()) {
+ mNewNotifPipeline.get().initialize(mNotificationListener);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 95ae23c..bcfbdac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -314,7 +314,6 @@
// adb shell settings put system enable_fullscreen_user_switcher 1 # Turn it on.
// Restart SystemUI or adb reboot.
final int DEFAULT = -1;
- // TODO(b/140061064)
final int overrideUseFullscreenUserSwitcher =
whitelistIpcs(() -> Settings.System.getInt(mContext.getContentResolver(),
"enable_fullscreen_user_switcher", DEFAULT));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
new file mode 100644
index 0000000..e4a67db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.util.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.Arrays;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotifCollectionTest extends SysuiTestCase {
+
+ @Mock private IStatusBarService mStatusBarService;
+ @Mock private NotificationListener mListenerService;
+ @Spy private RecordingCollectionListener mCollectionListener;
+
+ @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
+ @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
+ @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3");
+
+ @Captor private ArgumentCaptor<NotifServiceListener> mListenerCaptor;
+ @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor;
+
+ private NotifCollection mCollection;
+ private NotifServiceListener mServiceListener;
+
+ private NoManSimulator mNoMan;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Assert.sMainLooper = TestableLooper.get(this).getLooper();
+
+ mCollection = new NotifCollection(mStatusBarService);
+ mCollection.attach(mListenerService);
+ mCollection.addCollectionListener(mCollectionListener);
+
+ // Capture the listener object that the collection registers with the listener service so
+ // we can simulate listener service events in tests below
+ verify(mListenerService).setDownstreamListener(mListenerCaptor.capture());
+ mServiceListener = checkNotNull(mListenerCaptor.getValue());
+
+ mNoMan = new NoManSimulator(mServiceListener);
+ }
+
+ @Test
+ public void testEventDispatchedWhenNotifPosted() {
+ // WHEN a notification is posted
+ PostedNotif notif1 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 3)
+ .setRank(4747));
+
+ // THEN the listener is notified
+ verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
+
+ NotificationEntry entry = mEntryCaptor.getValue();
+ assertEquals(notif1.key, entry.key());
+ assertEquals(notif1.sbn, entry.sbn());
+ assertEquals(notif1.ranking, entry.ranking());
+ }
+
+ @Test
+ public void testEventDispatchedWhenNotifUpdated() {
+ // GIVEN a collection with one notif
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(4747));
+
+ // WHEN the notif is reposted
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(89));
+
+ // THEN the listener is notified
+ verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture());
+
+ NotificationEntry entry = mEntryCaptor.getValue();
+ assertEquals(notif2.key, entry.key());
+ assertEquals(notif2.sbn, entry.sbn());
+ assertEquals(notif2.ranking, entry.ranking());
+ }
+
+ @Test
+ public void testEventDispatchedWhenNotifRemoved() {
+ // GIVEN a collection with one notif
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+ clearInvocations(mCollectionListener);
+
+ PostedNotif notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+ clearInvocations(mCollectionListener);
+
+ // WHEN a notif is retracted
+ mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
+
+ // THEN the listener is notified
+ verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false);
+ assertEquals(notif.sbn, entry.sbn());
+ assertEquals(notif.ranking, entry.ranking());
+ }
+
+ @Test
+ public void testRankingsAreUpdatedForOtherNotifs() {
+ // GIVEN a collection with one notif
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(47));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN a new notif is posted, triggering a rerank
+ mNoMan.setRanking(notif1.sbn.getKey(), new RankingBuilder(notif1.ranking)
+ .setRank(56)
+ .build());
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 77));
+
+ // THEN the ranking is updated on the first entry
+ assertEquals(56, entry1.ranking().getRank());
+ }
+
+ @Test
+ public void testRankingUpdateIsProperlyIssuedToEveryone() {
+ // GIVEN a collection with a couple notifs
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
+ .setRank(3));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 8)
+ .setRank(2));
+ PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 77)
+ .setRank(1));
+
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key);
+
+ // WHEN a ranking update is delivered
+ Ranking newRanking1 = new RankingBuilder(notif1.ranking)
+ .setRank(4)
+ .setExplanation("Foo bar")
+ .build();
+ Ranking newRanking2 = new RankingBuilder(notif2.ranking)
+ .setRank(5)
+ .setExplanation("baz buzz")
+ .build();
+ Ranking newRanking3 = new RankingBuilder(notif3.ranking)
+ .setRank(6)
+ .setExplanation("Penguin pizza")
+ .build();
+
+ mNoMan.setRanking(notif1.sbn.getKey(), newRanking1);
+ mNoMan.setRanking(notif2.sbn.getKey(), newRanking2);
+ mNoMan.setRanking(notif3.sbn.getKey(), newRanking3);
+ mNoMan.issueRankingUpdate();
+
+ // THEN all of the NotifEntries have their rankings properly updated
+ assertEquals(newRanking1, entry1.ranking());
+ assertEquals(newRanking2, entry2.ranking());
+ assertEquals(newRanking3, entry3.ranking());
+ }
+
+ @Test
+ public void testNotifEntriesAreNotPersistedAcrossRemovalAndReposting() {
+ // GIVEN a notification that has been posted
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN the notification is retracted and then reposted
+ mNoMan.retractNotif(notif1.sbn, REASON_APP_CANCEL);
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+
+ // THEN the new NotificationEntry is a new object
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif1.key);
+ assertNotEquals(entry2, entry1);
+ }
+
+ @Test
+ public void testDismissNotification() throws RemoteException {
+ // GIVEN a collection with a couple notifications and a lifetime extender
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN a notification is manually dismissed
+ DismissedByUserStats stats = new DismissedByUserStats(
+ NotificationStats.DISMISSAL_SHADE,
+ NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(entry2.key(), 7, 2, true));
+
+ mCollection.dismissNotification(entry2, REASON_CLICK, stats);
+
+ // THEN we check for lifetime extension
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK);
+
+ // THEN we send the dismissal to system server
+ verify(mStatusBarService).onNotificationClear(
+ notif2.sbn.getPackageName(),
+ notif2.sbn.getTag(),
+ 88,
+ notif2.sbn.getUser().getIdentifier(),
+ notif2.sbn.getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+
+ // THEN we fire a remove event
+ verify(mCollectionListener).onEntryRemoved(entry2, REASON_CLICK, true);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDismissingNonExistentNotificationThrows() {
+ // GIVEN a collection that originally had three notifs, but where one was dismissed
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+
+ // WHEN we try to dismiss a notification that isn't present
+ mCollection.dismissNotification(
+ entry2,
+ REASON_CLICK,
+ new DismissedByUserStats(0, 0, NotificationVisibility.obtain("foo", 47, 3, true)));
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testLifetimeExtendersAreQueriedWhenNotifRemoved() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN a notification is removed
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+
+ // THEN each extender is asked whether to extend, even if earlier ones return true
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+
+ // THEN the entry is not removed
+ assertTrue(mCollection.getNotifs().contains(entry2));
+
+ // THEN the entry properly records all extenders that returned true
+ assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
+ }
+
+ @Test
+ public void testWhenLastLifetimeExtenderExpiresAllAreReQueried() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by one of them
+ mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN the last active extender expires (but new ones become active)
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = false;
+ mExtender3.shouldExtendLifetime = true;
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+
+ // THEN each extender is re-queried
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+
+ // THEN the entry is not removed
+ assertTrue(mCollection.getNotifs().contains(entry2));
+
+ // THEN the entry properly records all extenders that returned true
+ assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
+ }
+
+ @Test
+ public void testExtendersAreNotReQueriedUntilFinalActiveExtenderExpires() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN one (but not all) of the extenders expires
+ mExtender2.shouldExtendLifetime = false;
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+
+ // THEN the entry is not removed
+ assertTrue(mCollection.getNotifs().contains(entry2));
+
+ // THEN we don't re-query the extenders
+ verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
+ verify(mExtender2, never()).shouldExtendLifetime(eq(entry2), anyInt());
+ verify(mExtender3, never()).shouldExtendLifetime(eq(entry2), anyInt());
+
+ // THEN the entry properly records all extenders that returned true
+ assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
+ }
+
+ @Test
+ public void testNotificationIsRemovedWhenAllLifetimeExtendersExpire() {
+ // GIVEN a couple notifications and a few lifetime extenders
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN all of the active extenders expire
+ mExtender2.shouldExtendLifetime = false;
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+ mExtender1.shouldExtendLifetime = false;
+ mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
+
+ // THEN the entry removed
+ assertFalse(mCollection.getNotifs().contains(entry2));
+ verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
+ }
+
+ @Test
+ public void testLifetimeExtensionIsCanceledWhenNotifIsUpdated() {
+ // GIVEN a few lifetime extenders and a couple notifications
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN the notification is reposted
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+
+ // THEN all of the active lifetime extenders are canceled
+ verify(mExtender1).cancelLifetimeExtension(entry2);
+ verify(mExtender2).cancelLifetimeExtension(entry2);
+
+ // THEN the notification is still present
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testReentrantCallsToLifetimeExtendersThrow() {
+ // GIVEN a few lifetime extenders and a couple notifications
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
+ mExtender2.onCancelLifetimeExtension = () -> {
+ mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
+ };
+ // This triggers the call to cancelLifetimeExtension()
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testRankingIsUpdatedWhenALifetimeExtendedNotifIsReposted() {
+ // GIVEN a few lifetime extenders and a couple notifications
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mCollection.addNotificationLifetimeExtender(mExtender2);
+ mCollection.addNotificationLifetimeExtender(mExtender3);
+
+ mExtender1.shouldExtendLifetime = true;
+ mExtender2.shouldExtendLifetime = true;
+
+ PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
+ PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // GIVEN a notification gets lifetime-extended by a couple of them
+ mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ assertTrue(mCollection.getNotifs().contains(entry2));
+ clearInvocations(mExtender1, mExtender2, mExtender3);
+
+ // WHEN the notification is reposted
+ PostedNotif notif2a = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)
+ .setRank(4747)
+ .setExplanation("Some new explanation"));
+
+ // THEN the notification's ranking is properly updated
+ assertEquals(notif2a.ranking, entry2.ranking());
+ }
+
+ private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
+ return new NotificationEntryBuilder()
+ .setPkg(pkg)
+ .setId(id)
+ .setTag(tag);
+ }
+
+ private static NotificationEntryBuilder buildNotif(String pkg, int id) {
+ return new NotificationEntryBuilder()
+ .setPkg(pkg)
+ .setId(id);
+ }
+
+ private static class NoManSimulator {
+ private final NotifServiceListener mListener;
+ private final Map<String, Ranking> mRankings = new ArrayMap<>();
+
+ private NoManSimulator(
+ NotifServiceListener listener) {
+ mListener = listener;
+ }
+
+ PostedNotif postNotif(NotificationEntryBuilder builder) {
+ NotificationEntry entry = builder.build();
+ mRankings.put(entry.key(), entry.ranking());
+ mListener.onNotificationPosted(entry.sbn(), buildRankingMap());
+ return new PostedNotif(entry.sbn(), entry.ranking());
+ }
+
+ void retractNotif(StatusBarNotification sbn, int reason) {
+ assertNotNull(mRankings.remove(sbn.getKey()));
+ mListener.onNotificationRemoved(sbn, buildRankingMap(), reason);
+ }
+
+ void issueRankingUpdate() {
+ mListener.onNotificationRankingUpdate(buildRankingMap());
+ }
+
+ void setRanking(String key, Ranking ranking) {
+ mRankings.put(key, ranking);
+ }
+
+ private RankingMap buildRankingMap() {
+ return new RankingMap(mRankings.values().toArray(new Ranking[0]));
+ }
+ }
+
+ private static class PostedNotif {
+ public final String key;
+ public final StatusBarNotification sbn;
+ public final Ranking ranking;
+
+ private PostedNotif(StatusBarNotification sbn,
+ Ranking ranking) {
+ this.key = sbn.getKey();
+ this.sbn = sbn;
+ this.ranking = ranking;
+ }
+ }
+
+ private static class RecordingCollectionListener implements NotifCollectionListener {
+ private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
+
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ mLastSeenEntries.put(entry.key(), entry);
+ }
+
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+ }
+
+ public NotificationEntry getEntry(String key) {
+ if (!mLastSeenEntries.containsKey(key)) {
+ throw new RuntimeException("Key not found: " + key);
+ }
+ return mLastSeenEntries.get(key);
+ }
+ }
+
+ private static class RecordingLifetimeExtender implements NotifLifetimeExtender {
+ private final String mName;
+
+ public @Nullable OnEndLifetimeExtensionCallback callback;
+ public boolean shouldExtendLifetime = false;
+ public @Nullable Runnable onCancelLifetimeExtension;
+
+ private RecordingLifetimeExtender(String name) {
+ mName = name;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public void setCallback(OnEndLifetimeExtensionCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public boolean shouldExtendLifetime(
+ NotificationEntry entry,
+ @CancellationReason int reason) {
+ return shouldExtendLifetime;
+ }
+
+ @Override
+ public void cancelLifetimeExtension(NotificationEntry entry) {
+ if (onCancelLifetimeExtension != null) {
+ onCancelLifetimeExtension.run();
+ }
+ }
+ }
+
+ private static final String TEST_PACKAGE = "com.android.test.collection";
+ private static final String TEST_PACKAGE2 = "com.android.test.collection2";
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 8f1b6017..52b3720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -91,6 +91,7 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationEntryBuilder;
@@ -107,7 +108,7 @@
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.NewNotifPipeline;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -156,6 +157,7 @@
private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private CommandQueue mCommandQueue;
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private LightBarController mLightBarController;
@Mock private StatusBarIconController mStatusBarIconController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -205,7 +207,7 @@
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private InjectionInflationController mInjectionInflationController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
- @Mock private NotifPipelineInitializer mNotifPipelineInitializer;
+ @Mock private NewNotifPipeline mNewNotifPipeline;
@Mock private ZenModeController mZenModeController;
@Mock private AutoHideController mAutoHideController;
@Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@@ -288,6 +290,7 @@
mStatusBar = new StatusBar(
mContext,
+ mFeatureFlags,
mLightBarController,
mAutoHideController,
mKeyguardUpdateMonitor,
@@ -302,7 +305,7 @@
mDynamicPrivacyController,
mBypassHeadsUpNotifier,
true,
- mNotifPipelineInitializer,
+ () -> mNewNotifPipeline,
new FalsingManagerFake(),
mBroadcastDispatcher,
new RemoteInputQuickSettingsDisabler(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e909e7a..68e11df32 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -366,9 +366,11 @@
if (userId != mCurrentUserId) {
return;
}
- AccessibilityUserState userState = getUserStateLocked(userId);
- boolean reboundAService = userState.getBindingServicesLocked().removeIf(
+ final AccessibilityUserState userState = getUserStateLocked(userId);
+ final boolean reboundAService = userState.getBindingServicesLocked().removeIf(
component -> component != null
+ && component.getPackageName().equals(packageName))
+ || userState.mCrashedServices.removeIf(component -> component != null
&& component.getPackageName().equals(packageName));
if (reboundAService) {
onUserStateChangedLocked(userState);
@@ -393,6 +395,7 @@
if (compPkg.equals(packageName)) {
it.remove();
userState.getBindingServicesLocked().remove(comp);
+ userState.getCrashedServicesLocked().remove(comp);
// Update the enabled services setting.
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -753,6 +756,7 @@
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
userState.getBindingServicesLocked().clear();
+ userState.getCrashedServicesLocked().clear();
userState.mTouchExplorationGrantedServices.clear();
userState.mTouchExplorationGrantedServices.add(service);
@@ -1178,6 +1182,10 @@
AccessibilityServiceInfo accessibilityServiceInfo;
try {
accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
+ if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
+ // Restore the crashed attribute.
+ accessibilityServiceInfo.crashed = true;
+ }
mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
} catch (XmlPullParserException | IOException xppe) {
Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
@@ -1418,8 +1426,9 @@
continue;
}
- // Wait for the binding if it is in process.
- if (userState.getBindingServicesLocked().contains(componentName)) {
+ // Skip the component since it may be in process or crashed.
+ if (userState.getBindingServicesLocked().contains(componentName)
+ || userState.getCrashedServicesLocked().contains(componentName)) {
continue;
}
if (userState.mEnabledServices.contains(componentName)
@@ -2687,6 +2696,7 @@
}
} else if (mEnabledAccessibilityServicesUri.equals(uri)) {
if (readEnabledAccessibilityServicesLocked(userState)) {
+ userState.updateCrashedServicesIfNeededLocked();
onUserStateChangedLocked(userState);
}
} else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index d154060..a0a755a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -62,9 +62,6 @@
private final Handler mMainHandler;
- private boolean mWasConnectedAndDied;
-
-
AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
@@ -168,8 +165,6 @@
@Override
public AccessibilityServiceInfo getServiceInfo() {
- // Update crashed data
- mAccessibilityServiceInfo.crashed = mWasConnectedAndDied;
return mAccessibilityServiceInfo;
}
@@ -178,10 +173,13 @@
synchronized (mLock) {
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
- Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
- if (bindingServices.contains(mComponentName) || mWasConnectedAndDied) {
+ final Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
+ final Set<ComponentName> crashedServices = userState.getCrashedServicesLocked();
+ if (bindingServices.contains(mComponentName)
+ || crashedServices.contains(mComponentName)) {
bindingServices.remove(mComponentName);
- mWasConnectedAndDied = false;
+ crashedServices.remove(mComponentName);
+ mAccessibilityServiceInfo.crashed = false;
serviceInterface = mServiceInterface;
}
// There's a chance that service is removed from enabled_accessibility_services setting
@@ -271,7 +269,7 @@
if (!isConnectedLocked()) {
return;
}
- mWasConnectedAndDied = true;
+ mAccessibilityServiceInfo.crashed = true;
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState != null) {
userState.serviceDisconnectedLocked(this);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 69f1e0e..a0b9866 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -73,6 +73,8 @@
final Set<ComponentName> mBindingServices = new HashSet<>();
+ final Set<ComponentName> mCrashedServices = new HashSet<>();
+
final Set<ComponentName> mEnabledServices = new HashSet<>();
final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
@@ -127,6 +129,7 @@
// Clear service management state.
mBoundServices.clear();
mBindingServices.clear();
+ mCrashedServices.clear();
// Clear event management state.
mLastSentClientState = -1;
@@ -184,15 +187,16 @@
/**
* Make sure a services disconnected but still 'on' state is reflected in AccessibilityUserState
- * There are three states to a service here: off, bound, and binding.
- * This drops a service from a bound state, to the binding state.
- * The binding state describes the situation where a service is on, but not bound.
+ * There are four states to a service here: off, bound, and binding, and crashed.
+ * This drops a service from a bound state, to the crashed state.
+ * The crashed state describes the situation where a service used to be bound, but no longer is
+ * despite still being enabled.
*
* @param serviceConnection The service.
*/
void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
removeServiceLocked(serviceConnection);
- mBindingServices.add(serviceConnection.getComponentName());
+ mCrashedServices.add(serviceConnection.getComponentName());
}
/**
@@ -289,17 +293,44 @@
mBindInstantServiceAllowed = allowed;
}
+ /**
+ * Returns binding service list.
+ */
Set<ComponentName> getBindingServicesLocked() {
return mBindingServices;
}
/**
+ * Returns crashed service list.
+ */
+ Set<ComponentName> getCrashedServicesLocked() {
+ return mCrashedServices;
+ }
+
+ /**
* Returns enabled service list.
*/
Set<ComponentName> getEnabledServicesLocked() {
return mEnabledServices;
}
+ /**
+ * Remove service from crashed service list if users disable it.
+ */
+ void updateCrashedServicesIfNeededLocked() {
+ for (int i = 0, count = mInstalledServices.size(); i < count; i++) {
+ final AccessibilityServiceInfo installedService = mInstalledServices.get(i);
+ final ComponentName componentName = ComponentName.unflattenFromString(
+ installedService.getId());
+
+ if (mCrashedServices.contains(componentName)
+ && !mEnabledServices.contains(componentName)) {
+ // Remove it from mCrashedServices since users toggle the switch bar to retry.
+ mCrashedServices.remove(componentName);
+ }
+ }
+ }
+
List<AccessibilityServiceConnection> getBoundServicesLocked() {
return mBoundServices;
}
@@ -439,6 +470,18 @@
pw.append(componentName.toShortString());
}
}
+ pw.println("}");
+ pw.append(" Crashed services:{");
+ it = mCrashedServices.iterator();
+ if (it.hasNext()) {
+ ComponentName componentName = it.next();
+ pw.append(componentName.toShortString());
+ while (it.hasNext()) {
+ componentName = it.next();
+ pw.append(", ");
+ pw.append(componentName.toShortString());
+ }
+ }
pw.println("}]");
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index c8dbb36..27824af 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -18,11 +18,13 @@
import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE;
import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
+import static android.service.contentcapture.ContentCaptureService.setClientState;
import static android.view.contentcapture.ContentCaptureHelper.toList;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
import static com.android.internal.util.SyncResultReceiver.bundleFor;
@@ -520,6 +522,17 @@
return true;
}
+ @GuardedBy("mLock")
+ private boolean isDefaultServiceLocked(int userId) {
+ final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId);
+ if (defaultServiceName == null) {
+ return false;
+ }
+
+ final String currentServiceName = mServiceNameResolver.getServiceName(userId);
+ return defaultServiceName.equals(currentServiceName);
+ }
+
@Override // from AbstractMasterSystemService
protected void dumpLocked(String prefix, PrintWriter pw) {
super.dumpLocked(prefix, pw);
@@ -557,6 +570,10 @@
synchronized (mLock) {
final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+ if (!isDefaultServiceLocked(userId) && !isCalledByServiceLocked("startSession()")) {
+ setClientState(result, STATE_DISABLED, /* binder= */ null);
+ return;
+ }
service.startSessionLocked(activityToken, activityPresentationInfo, sessionId,
Binder.getCallingUid(), flags, result);
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 9a97ddb..b41e95f 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -47,6 +47,7 @@
import android.content.pm.PermissionInfo;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -1564,6 +1565,7 @@
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
mClockReceiver = mInjector.getClockReceiver(this);
+ new ChargingReceiver();
new InteractiveStateReceiver();
new UninstallReceiver();
@@ -4148,7 +4150,7 @@
public static final int LISTENER_TIMEOUT = 3;
public static final int REPORT_ALARMS_ACTIVE = 4;
public static final int APP_STANDBY_BUCKET_CHANGED = 5;
- public static final int APP_STANDBY_PAROLE_CHANGED = 6;
+ public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_STOPPED = 7;
public static final int UNREGISTER_CANCEL_LISTENER = 8;
@@ -4206,7 +4208,7 @@
}
break;
- case APP_STANDBY_PAROLE_CHANGED:
+ case CHARGING_STATUS_CHANGED:
synchronized (mLock) {
mAppStandbyParole = (Boolean) msg.obj;
if (reorderAlarmsBasedOnStandbyBuckets(null)) {
@@ -4247,6 +4249,37 @@
}
}
+ @VisibleForTesting
+ class ChargingReceiver extends BroadcastReceiver {
+ ChargingReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ getContext().registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final boolean charging;
+ if (BatteryManager.ACTION_CHARGING.equals(action)) {
+ if (DEBUG_STANDBY) {
+ Slog.d(TAG, "Device is charging.");
+ }
+ charging = true;
+ } else {
+ if (DEBUG_STANDBY) {
+ Slog.d(TAG, "Disconnected from power.");
+ }
+ charging = false;
+ }
+ mHandler.removeMessages(AlarmHandler.CHARGING_STATUS_CHANGED);
+ mHandler.obtainMessage(AlarmHandler.CHARGING_STATUS_CHANGED, charging)
+ .sendToTarget();
+ }
+ }
+
+ @VisibleForTesting
class ClockReceiver extends BroadcastReceiver {
public ClockReceiver() {
IntentFilter filter = new IntentFilter();
@@ -4429,7 +4462,7 @@
@Override public void onUidCachedChanged(int uid, boolean cached) {
}
- };
+ }
/**
* Tracking of app assignments to standby buckets
@@ -4447,18 +4480,7 @@
mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
.sendToTarget();
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- if (DEBUG_STANDBY) {
- Slog.d(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
- mHandler.removeMessages(AlarmHandler.APP_STANDBY_PAROLE_CHANGED);
- mHandler.obtainMessage(AlarmHandler.APP_STANDBY_PAROLE_CHANGED,
- Boolean.valueOf(isParoleOn)).sendToTarget();
- }
- };
+ }
private final Listener mForceAppStandbyListener = new Listener() {
@Override
diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java
index 2c67c50..da760b6 100644
--- a/services/core/java/com/android/server/AppStateTracker.java
+++ b/services/core/java/com/android/server/AppStateTracker.java
@@ -71,8 +71,7 @@
* - Temporary power save whitelist
* - Global "force all apps standby" mode enforced by battery saver.
*
- * Test:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
+ * Test: atest com.android.server.AppStateTrackerTest
*/
public class AppStateTracker {
private static final String TAG = "AppStateTracker";
@@ -710,10 +709,6 @@
mHandler.notifyExemptChanged();
}
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- }
}
private Listener[] cloneListeners() {
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 1a5dac5..e9d2b31 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -18,6 +18,23 @@
"include-filter": "com.android.server.appop"
}
]
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SplitPermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionFlagsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index b4c7dd3..080e6ce 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -333,7 +333,8 @@
current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
current.mPortId = getPortId(current.mPhysicalAddress);
current.mDeviceType = params[2] & 0xFF;
- current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType);
+ // Keep display name empty. TIF fallbacks to the service label provided by the package mg.
+ current.mDisplayName = "";
// This is to manager CEC device separately in case they don't have address.
if (mIsTvDevice) {
@@ -359,17 +360,13 @@
return;
}
- String displayName = null;
+ String displayName = "";
try {
- if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
- displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
- } else {
+ if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
displayName = new String(cmd.getParams(), "US-ASCII");
}
} catch (UnsupportedEncodingException e) {
Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
- // If failed to get display name, use the default name of device.
- displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
}
current.mDisplayName = displayName;
increaseProcessedDeviceCount();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 211d028..dde873b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -489,7 +489,7 @@
if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
addCecDevice(new HdmiDeviceInfo(
address, path, mService.pathToPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
+ Constants.UNKNOWN_VENDOR_ID, ""));
// if we are adding a new device info, send out a give osd name command
// to update the name of the device in TIF
mService.sendCecCommand(
@@ -526,7 +526,8 @@
return true;
}
- if (deviceInfo.getDisplayName().equals(osdName)) {
+ if (deviceInfo.getDisplayName() != null
+ && deviceInfo.getDisplayName().equals(osdName)) {
Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
return true;
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 63ba138..34fb641 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2071,7 +2071,7 @@
// since the user never unlock the device manually. In this case, always
// return a default metrics object. This is to distinguish this case from
// the case where during boot user password is unknown yet (returning null here)
- return new PasswordMetrics();
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
}
synchronized (this) {
return mUserPasswordMetrics.get(userHandle);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 9eac252..4816ceb 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -43,7 +43,7 @@
* Maintains a connection to a particular media route provider service.
*/
final class MediaRoute2ProviderProxy implements ServiceConnection {
- private static final String TAG = "MediaRoute2Provider";
+ private static final String TAG = "MR2ProviderProxy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
@@ -274,7 +274,9 @@
.setUniqueId(mUniqueId)
.build();
}
- mHandler.post(mStateChanged);
+ if (mCallback != null) {
+ mCallback.onProviderStateChanged(MediaRoute2ProviderProxy.this);
+ }
}
private void disconnect() {
@@ -291,15 +293,6 @@
return "Service connection " + mComponentName.flattenToShortString();
}
- private final Runnable mStateChanged = new Runnable() {
- @Override
- public void run() {
- if (mCallback != null) {
- mCallback.onProviderStateChanged(MediaRoute2ProviderProxy.this);
- }
- }
- };
-
public interface Callback {
void onProviderStateChanged(MediaRoute2ProviderProxy provider);
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 194015d..c95119d 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -35,9 +35,10 @@
import java.util.Collections;
/**
+ * Watches changes of packages, or scan them for finding media route providers.
*/
final class MediaRoute2ProviderWatcher {
- private static final String TAG = "MediaRouteProvider"; // max. 23 chars
+ private static final String TAG = "MR2ProviderWatcher";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 74d59ac..361dc36 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -55,7 +55,7 @@
* TODO: Merge this to MediaRouterService once it's finished.
*/
class MediaRouter2ServiceImpl {
- private static final String TAG = "MediaRouter2ServiceImpl";
+ private static final String TAG = "MR2ServiceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
@@ -339,11 +339,11 @@
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = client.asBinder();
if (mAllClientRecords.get(binder) == null) {
- boolean newUser = false;
UserRecord userRecord = mUserRecords.get(userId);
if (userRecord == null) {
userRecord = new UserRecord(userId);
- newUser = true;
+ mUserRecords.put(userId, userRecord);
+ initializeUserLocked(userRecord);
}
Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
packageName, trusted);
@@ -353,11 +353,6 @@
throw new RuntimeException("Media router client died prematurely.", ex);
}
- if (newUser) {
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
- }
-
userRecord.mClientRecords.add(clientRecord);
mAllClientRecords.put(binder, clientRecord);
@@ -815,8 +810,6 @@
if (service == null) {
return;
}
- final List<IMediaRouter2Manager> managers = new ArrayList<>();
- final List<IMediaRouter2Client> clients = new ArrayList<>();
final List<MediaRoute2ProviderInfo> providers = new ArrayList<>();
for (MediaRoute2ProviderProxy mediaProvider : mMediaProviders) {
final MediaRoute2ProviderInfo providerInfo =
@@ -829,6 +822,8 @@
}
mProviderInfos = providers;
+ final List<IMediaRouter2Manager> managers = new ArrayList<>();
+ final List<IMediaRouter2Client> clients = new ArrayList<>();
synchronized (service.mLock) {
for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
managers.add(managerRecord.mManager);
@@ -878,9 +873,8 @@
}
List<IMediaRouter2Manager> managers = new ArrayList<>();
synchronized (service.mLock) {
- final int count = mUserRecord.mManagerRecords.size();
- for (int i = 0; i < count; i++) {
- managers.add(mUserRecord.mManagerRecords.get(i).mManager);
+ for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
+ managers.add(managerRecord.mManager);
}
}
for (IMediaRouter2Manager manager : managers) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 812ce32..09be474 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -797,6 +797,7 @@
writePolicyAL();
}
+ enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, true);
setRestrictBackgroundUL(mLoadedRestrictBackground);
updateRulesForGlobalChangeAL(false);
updateNotificationsNL();
@@ -3830,39 +3831,6 @@
}
/**
- * Toggle the firewall standby chain and inform listeners if the uid rules have effectively
- * changed.
- */
- @GuardedBy("mUidRulesFirstLock")
- void updateRulesForAppIdleParoleUL() {
- boolean paroled = mUsageStats.isAppIdleParoleOn();
- boolean enableChain = !paroled;
- enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, enableChain);
-
- int ruleCount = mUidFirewallStandbyRules.size();
- for (int i = 0; i < ruleCount; i++) {
- int uid = mUidFirewallStandbyRules.keyAt(i);
- int oldRules = mUidRules.get(uid);
- if (enableChain) {
- // Chain wasn't enabled before and the other power-related
- // chains are whitelists, so we can clear the
- // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
- // the effective rules result in blocking network access.
- oldRules &= MASK_METERED_NETWORKS;
- } else {
- // Skip if it had no restrictions to begin with
- if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
- }
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules, paroled);
- if (newUidRules == RULE_NONE) {
- mUidRules.delete(uid);
- } else {
- mUidRules.put(uid, newUidRules);
- }
- }
- }
-
- /**
* Update rules that might be changed by {@link #mRestrictBackground},
* {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
@@ -4317,7 +4285,7 @@
private void updateRulesForPowerRestrictionsUL(int uid) {
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false);
+ final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules);
if (newUidRules == RULE_NONE) {
mUidRules.delete(uid);
@@ -4331,30 +4299,28 @@
*
* @param uid the uid of the app to update rules for
* @param oldUidRules the current rules for the uid, in order to determine if there's a change
- * @param paroled whether to ignore idle state of apps and only look at other restrictions.
*
* @return the new computed rules for the uid
*/
- private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
+ private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
- "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
- + (paroled ? "P" : "-"));
+ "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules);
}
try {
- return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
+ return updateRulesForPowerRestrictionsULInner(uid, oldUidRules);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
- private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
+ private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
}
- final boolean isIdle = !paroled && isUidIdle(uid);
+ final boolean isIdle = isUidIdle(uid);
final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
@@ -4426,14 +4392,6 @@
} catch (NameNotFoundException nnfe) {
}
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- synchronized (mUidRulesFirstLock) {
- mLogger.paroleStateChanged(isParoleOn);
- updateRulesForAppIdleParoleUL();
- }
- }
}
private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
@@ -4775,7 +4733,7 @@
}
/**
- * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
+ * Calls {@link #setUidFirewallRulesUL(int, SparseIntArray)} and
* {@link #enableFirewallChainUL(int, boolean)} synchronously.
*
* @param chain firewall chain.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ca979f8..cd3343b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5112,8 +5112,8 @@
}
if (contentViewSize >= mStripRemoteViewsSizeBytes) {
mUsageStats.registerImageRemoved(pkg);
- Slog.w(TAG,
- "Removed too large RemoteViews on pkg: " + pkg + " tag: " + tag + " id: " + id);
+ Slog.w(TAG, "Removed too large RemoteViews (" + contentViewSize + " bytes) on pkg: "
+ + pkg + " tag: " + tag + " id: " + id);
return true;
}
return false;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index eb648b3..befe4e9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -548,12 +548,16 @@
private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
@Override
- public void onUserSwitching(int newUserId) throws RemoteException {}
+ public void onUserSwitching(@UserIdInt int newUserId) throws RemoteException {
+ synchronized (mLock) {
+ mUserId = newUserId;
+ }
+ }
@Override
public void onForegroundProfileSwitch(@UserIdInt int newProfileId) throws RemoteException {
final long now = SystemClock.uptimeMillis();
- synchronized(mLock) {
+ synchronized (mLock) {
mForegroundProfile = newProfileId;
maybeUpdateForegroundProfileLastActivityLocked(now);
}
@@ -562,6 +566,8 @@
// User id corresponding to activity the user is currently interacting with.
private @UserIdInt int mForegroundProfile;
+ // User id of main profile for the current user (doesn't include managed profiles)
+ private @UserIdInt int mUserId;
// Per-profile state to track when a profile should be locked.
private final SparseArray<ProfilePowerState> mProfilePowerState = new SparseArray<>();
@@ -1792,9 +1798,9 @@
if (mBootCompleted) {
if (mIsPowered && !BatteryManager.isPlugWired(oldPlugType)
&& BatteryManager.isPlugWired(mPlugType)) {
- mNotifier.onWiredChargingStarted(mForegroundProfile);
+ mNotifier.onWiredChargingStarted(mUserId);
} else if (dockedOnWirelessCharger) {
- mNotifier.onWirelessChargingStarted(mBatteryLevel, mForegroundProfile);
+ mNotifier.onWirelessChargingStarted(mBatteryLevel, mUserId);
}
}
}
@@ -3493,6 +3499,7 @@
pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled);
pw.println(" mForegroundProfile=" + mForegroundProfile);
+ pw.println(" mUserId=" + mUserId);
final long sleepTimeout = getSleepTimeoutLocked();
final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3cdb59b..3663f46 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1228,6 +1228,7 @@
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
+ mContext.getMainThreadHandler().removeCallbacks(this::tryToRebind);
}
}
}
@@ -1270,6 +1271,34 @@
}
}
+ private void tryToRebind() {
+ synchronized (mLock) {
+ if (mWallpaper.wallpaperUpdating) {
+ return;
+ }
+ final ComponentName wpService = mWallpaper.wallpaperComponent;
+ // The broadcast of package update could be delayed after service disconnected. Try
+ // to re-bind the service for 10 seconds.
+ if (bindWallpaperComponentLocked(
+ wpService, true, false, mWallpaper, null)) {
+ mWallpaper.connection.scheduleTimeoutLocked();
+ } else if (SystemClock.uptimeMillis() - mWallpaper.lastDiedTime
+ < WALLPAPER_RECONNECT_TIMEOUT_MS) {
+ // Bind fail without timeout, schedule rebind
+ Slog.w(TAG, "Rebind fail! Try again later");
+ mContext.getMainThreadHandler().postDelayed(this::tryToRebind, 1000);
+ } else {
+ // Timeout
+ Slog.w(TAG, "Reverting to built-in wallpaper!");
+ clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ final String flattened = wpService.flattenToString();
+ EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
+ flattened.substring(0, Math.min(flattened.length(),
+ MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
+ }
+ }
+ }
+
private void processDisconnect(final ServiceConnection connection) {
synchronized (mLock) {
// The wallpaper disappeared. If this isn't a system-default one, track
@@ -1293,20 +1322,8 @@
clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
} else {
mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
-
- clearWallpaperComponentLocked(mWallpaper);
- if (bindWallpaperComponentLocked(
- wpService, false, false, mWallpaper, null)) {
- mWallpaper.connection.scheduleTimeoutLocked();
- } else {
- Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
- }
+ tryToRebind();
}
- final String flattened = wpService.flattenToString();
- EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
- flattened.substring(0, Math.min(flattened.length(),
- MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
}
} else {
if (DEBUG_LIVE) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1a80006..c505454 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1834,7 +1834,9 @@
*/
private void complyActivityFlags(TaskRecord targetTask, ActivityRecord reusedActivity) {
ActivityRecord targetTaskTop = targetTask.getTopActivity();
- if (reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+ final boolean resetTask =
+ reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0;
+ if (resetTask) {
targetTaskTop = mTargetStack.resetTaskIfNeededLocked(targetTaskTop, mStartActivity);
}
@@ -1926,7 +1928,7 @@
} else if (reusedActivity == null) {
mAddingToTask = true;
}
- } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
+ } else if (!resetTask) {
// In this case an activity is being launched in to an existing task, without
// resetting that task. This is typically the situation of launching an activity
// from a notification or shortcut. We want to place the new activity on top of the
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0ae205a..6f643c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
import android.app.admin.DeviceStateCache;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.StartInstallingUpdateCallback;
@@ -255,6 +256,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
@@ -989,19 +991,8 @@
static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
- static final int DEF_MINIMUM_PASSWORD_LENGTH = 0;
- static final int DEF_MINIMUM_PASSWORD_LETTERS = 1;
- static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0;
- static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0;
- static final int DEF_MINIMUM_PASSWORD_NUMERIC = 1;
- static final int DEF_MINIMUM_PASSWORD_SYMBOLS = 1;
- static final int DEF_MINIMUM_PASSWORD_NON_LETTER = 0;
@NonNull
- PasswordMetrics minimumPasswordMetrics = new PasswordMetrics(
- PASSWORD_QUALITY_UNSPECIFIED, DEF_MINIMUM_PASSWORD_LENGTH,
- DEF_MINIMUM_PASSWORD_LETTERS, DEF_MINIMUM_PASSWORD_UPPER_CASE,
- DEF_MINIMUM_PASSWORD_LOWER_CASE, DEF_MINIMUM_PASSWORD_NUMERIC,
- DEF_MINIMUM_PASSWORD_SYMBOLS, DEF_MINIMUM_PASSWORD_NON_LETTER);
+ PasswordPolicy mPasswordPolicy = new PasswordPolicy();
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -1136,36 +1127,36 @@
out.startTag(null, TAG_POLICIES);
info.writePoliciesToXml(out);
out.endTag(null, TAG_POLICIES);
- if (minimumPasswordMetrics.quality != PASSWORD_QUALITY_UNSPECIFIED) {
+ if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
writeAttributeValueToXml(
- out, TAG_PASSWORD_QUALITY, minimumPasswordMetrics.quality);
- if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
+ out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
+ if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LENGTH, minimumPasswordMetrics.length);
+ out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
}
- if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
+ if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_UPPERCASE, minimumPasswordMetrics.upperCase);
+ out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
}
- if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
+ if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LOWERCASE, minimumPasswordMetrics.lowerCase);
+ out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
}
- if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
+ if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LETTERS, minimumPasswordMetrics.letters);
+ out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
}
- if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
+ if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_NUMERIC, minimumPasswordMetrics.numeric);
+ out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
}
- if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
+ if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_SYMBOLS, minimumPasswordMetrics.symbols);
+ out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
}
- if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
+ if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_NONLETTER, minimumPasswordMetrics.nonLetter);
+ out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
}
}
if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
@@ -1404,31 +1395,31 @@
info.readPoliciesFromXml(parser);
}
} else if (TAG_PASSWORD_QUALITY.equals(tag)) {
- minimumPasswordMetrics.quality = Integer.parseInt(
+ mPasswordPolicy.quality = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
- minimumPasswordMetrics.length = Integer.parseInt(
+ mPasswordPolicy.length = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
passwordHistoryLength = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
- minimumPasswordMetrics.upperCase = Integer.parseInt(
+ mPasswordPolicy.upperCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
- minimumPasswordMetrics.lowerCase = Integer.parseInt(
+ mPasswordPolicy.lowerCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
- minimumPasswordMetrics.letters = Integer.parseInt(
+ mPasswordPolicy.letters = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
- minimumPasswordMetrics.numeric = Integer.parseInt(
+ mPasswordPolicy.numeric = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
- minimumPasswordMetrics.symbols = Integer.parseInt(
+ mPasswordPolicy.symbols = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
- minimumPasswordMetrics.nonLetter = Integer.parseInt(
+ mPasswordPolicy.nonLetter = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
}else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
@@ -1689,23 +1680,23 @@
pw.decreaseIndent();
}
pw.print("passwordQuality=0x");
- pw.println(Integer.toHexString(minimumPasswordMetrics.quality));
+ pw.println(Integer.toHexString(mPasswordPolicy.quality));
pw.print("minimumPasswordLength=");
- pw.println(minimumPasswordMetrics.length);
+ pw.println(mPasswordPolicy.length);
pw.print("passwordHistoryLength=");
pw.println(passwordHistoryLength);
pw.print("minimumPasswordUpperCase=");
- pw.println(minimumPasswordMetrics.upperCase);
+ pw.println(mPasswordPolicy.upperCase);
pw.print("minimumPasswordLowerCase=");
- pw.println(minimumPasswordMetrics.lowerCase);
+ pw.println(mPasswordPolicy.lowerCase);
pw.print("minimumPasswordLetters=");
- pw.println(minimumPasswordMetrics.letters);
+ pw.println(mPasswordPolicy.letters);
pw.print("minimumPasswordNumeric=");
- pw.println(minimumPasswordMetrics.numeric);
+ pw.println(mPasswordPolicy.numeric);
pw.print("minimumPasswordSymbols=");
- pw.println(minimumPasswordMetrics.symbols);
+ pw.println(mPasswordPolicy.symbols);
pw.print("minimumPasswordNonLetter=");
- pw.println(minimumPasswordMetrics.nonLetter);
+ pw.println(mPasswordPolicy.nonLetter);
pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print("strongAuthUnlockTimeout=");
@@ -4162,15 +4153,15 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
final long ident = mInjector.binderClearCallingIdentity();
try {
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.quality != quality) {
- metrics.quality = quality;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.quality != quality) {
+ passwordPolicy.quality = quality;
resetInactivePasswordRequirementsIfRPlus(userId, ap);
updatePasswordValidityCheckpointLocked(userId, parent);
updatePasswordQualityCacheForUserGroup(userId);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
@@ -4199,17 +4190,17 @@
*/
private void resetInactivePasswordRequirementsIfRPlus(int userId, ActiveAdmin admin) {
if (passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), userId)) {
- final PasswordMetrics metrics = admin.minimumPasswordMetrics;
- if (metrics.quality < PASSWORD_QUALITY_NUMERIC) {
- metrics.length = ActiveAdmin.DEF_MINIMUM_PASSWORD_LENGTH;
+ final PasswordPolicy policy = admin.mPasswordPolicy;
+ if (policy.quality < PASSWORD_QUALITY_NUMERIC) {
+ policy.length = PasswordPolicy.DEF_MINIMUM_LENGTH;
}
- if (metrics.quality < PASSWORD_QUALITY_COMPLEX) {
- metrics.letters = ActiveAdmin.DEF_MINIMUM_PASSWORD_LETTERS;
- metrics.upperCase = ActiveAdmin.DEF_MINIMUM_PASSWORD_UPPER_CASE;
- metrics.lowerCase = ActiveAdmin.DEF_MINIMUM_PASSWORD_LOWER_CASE;
- metrics.numeric = ActiveAdmin.DEF_MINIMUM_PASSWORD_NUMERIC;
- metrics.symbols = ActiveAdmin.DEF_MINIMUM_PASSWORD_SYMBOLS;
- metrics.nonLetter = ActiveAdmin.DEF_MINIMUM_PASSWORD_NON_LETTER;
+ if (policy.quality < PASSWORD_QUALITY_COMPLEX) {
+ policy.letters = PasswordPolicy.DEF_MINIMUM_LETTERS;
+ policy.upperCase = PasswordPolicy.DEF_MINIMUM_UPPER_CASE;
+ policy.lowerCase = PasswordPolicy.DEF_MINIMUM_LOWER_CASE;
+ policy.numeric = PasswordPolicy.DEF_MINIMUM_NUMERIC;
+ policy.symbols = PasswordPolicy.DEF_MINIMUM_SYMBOLS;
+ policy.nonLetter = PasswordPolicy.DEF_MINIMUM_NON_LETTER;
}
}
}
@@ -4274,7 +4265,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordMetrics.quality : mode;
+ return admin != null ? admin.mPasswordPolicy.quality : mode;
}
// Return the strictest policy across all participating admins.
@@ -4283,8 +4274,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (mode < admin.minimumPasswordMetrics.quality) {
- mode = admin.minimumPasswordMetrics.quality;
+ if (mode < admin.mPasswordPolicy.quality) {
+ mode = admin.mPasswordPolicy.quality;
}
}
return mode;
@@ -4344,14 +4335,14 @@
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_NUMERIC, "setPasswordMinimumLength");
- if (metrics.length != length) {
- metrics.length = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.length != length) {
+ passwordPolicy.length = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LENGTH)
@@ -4362,7 +4353,7 @@
private void ensureMinimumQuality(
int userId, ActiveAdmin admin, int minimumQuality, String operation) {
- if (admin.minimumPasswordMetrics.quality < minimumQuality
+ if (admin.mPasswordPolicy.quality < minimumQuality
&& passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
userId)) {
throw new IllegalStateException(String.format(
@@ -4373,7 +4364,7 @@
@Override
public int getPasswordMinimumLength(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.length, PASSWORD_QUALITY_NUMERIC);
+ admin -> admin.mPasswordPolicy.length, PASSWORD_QUALITY_NUMERIC);
}
@Override
@@ -4602,13 +4593,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumUpperCase");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.upperCase != length) {
- metrics.upperCase = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.upperCase != length) {
+ passwordPolicy.upperCase = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_UPPER_CASE)
@@ -4620,7 +4611,7 @@
@Override
public int getPasswordMinimumUpperCase(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.upperCase, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.upperCase, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4632,13 +4623,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumLowerCase");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.lowerCase != length) {
- metrics.lowerCase = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.lowerCase != length) {
+ passwordPolicy.lowerCase = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LOWER_CASE)
@@ -4650,7 +4641,7 @@
@Override
public int getPasswordMinimumLowerCase(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.lowerCase, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.lowerCase, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4664,13 +4655,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumLetters");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.letters != length) {
- metrics.letters = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.letters != length) {
+ passwordPolicy.letters = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LETTERS)
@@ -4682,7 +4673,7 @@
@Override
public int getPasswordMinimumLetters(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.letters, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.letters, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4696,13 +4687,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumNumeric");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.numeric != length) {
- metrics.numeric = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.numeric != length) {
+ passwordPolicy.numeric = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NUMERIC)
@@ -4714,7 +4705,7 @@
@Override
public int getPasswordMinimumNumeric(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.numeric, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.numeric, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4728,13 +4719,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumSymbols");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.symbols != length) {
- ap.minimumPasswordMetrics.symbols = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.symbols != length) {
+ ap.mPasswordPolicy.symbols = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_SYMBOLS)
@@ -4746,7 +4737,7 @@
@Override
public int getPasswordMinimumSymbols(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.symbols, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.symbols, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4761,13 +4752,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumNonLetter");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.nonLetter != length) {
- ap.minimumPasswordMetrics.nonLetter = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.nonLetter != length) {
+ ap.mPasswordPolicy.nonLetter = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NON_LETTER)
@@ -4779,7 +4770,7 @@
@Override
public int getPasswordMinimumNonLetter(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.nonLetter, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.nonLetter, PASSWORD_QUALITY_COMPLEX);
}
/**
@@ -4815,6 +4806,33 @@
}
}
+ /**
+ * Calculates strictest (maximum) value for a given password property enforced by admin[s].
+ */
+ @Override
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ return getPasswordMinimumMetrics(userHandle, false /* parent */);
+ }
+
+ /**
+ * Calculates strictest (maximum) value for a given password property enforced by admin[s].
+ */
+ private PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle, boolean parent) {
+ if (!mHasFeature) {
+ new PasswordMetrics(LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ }
+ enforceFullCrossUsersPermission(userHandle);
+ ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>();
+ synchronized (getLockObject()) {
+ List<ActiveAdmin> admins =
+ getActiveAdminsForLockscreenPoliciesLocked(userHandle, parent);
+ for (ActiveAdmin admin : admins) {
+ adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
+ }
+ }
+ return PasswordMetrics.merge(adminMetrics);
+ }
+
@Override
public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
if (!mHasFeature) {
@@ -4830,8 +4848,9 @@
int credentialOwner = getCredentialOwner(userHandle, parent);
DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
- return isActivePasswordSufficientForUserLocked(
+ boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
policy.mPasswordValidAtLastCheckpoint, metrics, userHandle, parent);
+ return activePasswordSufficientForUserLocked;
}
}
@@ -4894,25 +4913,11 @@
*/
private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
@NonNull PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
- final int requiredQuality = getPasswordQuality(null, userId, parent);
-
- if (requiredQuality >= PASSWORD_QUALITY_NUMERIC
- && metrics.length < getPasswordMinimumLength(null, userId, parent)) {
- return false;
- }
-
- // PASSWORD_QUALITY_COMPLEX doesn't represent actual password quality, it means that number
- // of characters of each class should be checked instead of quality itself.
- if (requiredQuality == PASSWORD_QUALITY_COMPLEX) {
- return metrics.upperCase >= getPasswordMinimumUpperCase(null, userId, parent)
- && metrics.lowerCase >= getPasswordMinimumLowerCase(null, userId, parent)
- && metrics.letters >= getPasswordMinimumLetters(null, userId, parent)
- && metrics.numeric >= getPasswordMinimumNumeric(null, userId, parent)
- && metrics.symbols >= getPasswordMinimumSymbols(null, userId, parent)
- && metrics.nonLetter >= getPasswordMinimumNonLetter(null, userId, parent);
- } else {
- return metrics.quality >= requiredQuality;
- }
+ PasswordMetrics minMetrics = getPasswordMinimumMetrics(userId, parent);
+ final List<PasswordValidationError> passwordValidationErrors =
+ PasswordMetrics.validatePasswordMetrics(
+ minMetrics, PASSWORD_COMPLEXITY_NONE, false, metrics);
+ return passwordValidationErrors.isEmpty();
}
@Override
@@ -5170,77 +5175,17 @@
private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
int flags, int callingUid, int userHandle) {
- int quality;
synchronized (getLockObject()) {
- quality = getPasswordQuality(null, userHandle, /* parent */ false);
- if (quality == PASSWORD_QUALITY_MANAGED) {
- quality = PASSWORD_QUALITY_UNSPECIFIED;
- }
// TODO(b/120484642): remove getBytes() below
- final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password.getBytes());
- final int realQuality = metrics.quality;
- if (realQuality < quality && quality != PASSWORD_QUALITY_COMPLEX) {
- Slog.w(LOG_TAG, "resetPassword: password quality 0x"
- + Integer.toHexString(realQuality)
- + " does not meet required quality 0x"
- + Integer.toHexString(quality));
+ final PasswordMetrics minMetrics = getPasswordMinimumMetrics(userHandle);
+ final List<PasswordValidationError> validationErrors =
+ PasswordMetrics.validatePassword(
+ minMetrics, PASSWORD_COMPLEXITY_NONE, false, password.getBytes());
+ if (!validationErrors.isEmpty()) {
+ Log.w(LOG_TAG, "Failed to reset password due to constraint violation: "
+ + validationErrors.get(0));
return false;
}
- quality = Math.max(realQuality, quality);
- int length = getPasswordMinimumLength(null, userHandle, /* parent */ false);
- if (password.length() < length) {
- Slog.w(LOG_TAG, "resetPassword: password length " + password.length()
- + " does not meet required length " + length);
- return false;
- }
- if (quality == PASSWORD_QUALITY_COMPLEX) {
- int neededLetters = getPasswordMinimumLetters(null, userHandle, /* parent */ false);
- if(metrics.letters < neededLetters) {
- Slog.w(LOG_TAG, "resetPassword: number of letters " + metrics.letters
- + " does not meet required number of letters " + neededLetters);
- return false;
- }
- int neededNumeric = getPasswordMinimumNumeric(null, userHandle, /* parent */ false);
- if (metrics.numeric < neededNumeric) {
- Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + metrics.numeric
- + " does not meet required number of numerical digits "
- + neededNumeric);
- return false;
- }
- int neededLowerCase = getPasswordMinimumLowerCase(
- null, userHandle, /* parent */ false);
- if (metrics.lowerCase < neededLowerCase) {
- Slog.w(LOG_TAG, "resetPassword: number of lowercase letters "
- + metrics.lowerCase
- + " does not meet required number of lowercase letters "
- + neededLowerCase);
- return false;
- }
- int neededUpperCase = getPasswordMinimumUpperCase(
- null, userHandle, /* parent */ false);
- if (metrics.upperCase < neededUpperCase) {
- Slog.w(LOG_TAG, "resetPassword: number of uppercase letters "
- + metrics.upperCase
- + " does not meet required number of uppercase letters "
- + neededUpperCase);
- return false;
- }
- int neededSymbols = getPasswordMinimumSymbols(null, userHandle, /* parent */ false);
- if (metrics.symbols < neededSymbols) {
- Slog.w(LOG_TAG, "resetPassword: number of special symbols " + metrics.symbols
- + " does not meet required number of special symbols " + neededSymbols);
- return false;
- }
- int neededNonLetter = getPasswordMinimumNonLetter(
- null, userHandle, /* parent */ false);
- if (metrics.nonLetter < neededNonLetter) {
- Slog.w(LOG_TAG, "resetPassword: number of non-letter characters "
- + metrics.nonLetter
- + " does not meet required number of non-letter characters "
- + neededNonLetter);
- return false;
- }
- }
}
DevicePolicyData policy = getUserData(userHandle);
@@ -11604,10 +11549,10 @@
/**
* Returns true if specified admin is allowed to limit passwords and has a
- * {@code minimumPasswordMetrics.quality} of at least {@code minPasswordQuality}
+ * {@code mPasswordPolicy.quality} of at least {@code minPasswordQuality}
*/
private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) {
- if (admin.minimumPasswordMetrics.quality < minPasswordQuality) {
+ if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
@@ -14226,13 +14171,13 @@
}
private void maybeLogPasswordComplexitySet(ComponentName who, int userId, boolean parent,
- PasswordMetrics metrics) {
+ PasswordPolicy passwordPolicy) {
if (SecurityLog.isLoggingEnabled()) {
final int affectedUserId = parent ? getProfileParentId(userId) : userId;
SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_COMPLEXITY_SET, who.getPackageName(),
- userId, affectedUserId, metrics.length, metrics.quality, metrics.letters,
- metrics.nonLetter, metrics.numeric, metrics.upperCase, metrics.lowerCase,
- metrics.symbols);
+ userId, affectedUserId, passwordPolicy.length, passwordPolicy.quality,
+ passwordPolicy.letters, passwordPolicy.nonLetter, passwordPolicy.numeric,
+ passwordPolicy.upperCase, passwordPolicy.lowerCase, passwordPolicy.symbols);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6e8b86a..7b7b8e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -34,7 +34,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
-import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -53,6 +53,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
@@ -68,6 +69,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -107,6 +109,7 @@
private long mAppStandbyWindow;
private AlarmManagerService mService;
private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener;
+ private AlarmManagerService.ChargingReceiver mChargingReceiver;
@Mock
private ContentResolver mMockResolver;
@Mock
@@ -290,6 +293,13 @@
ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture());
mAppStandbyListener = captor.getValue();
+
+ ArgumentCaptor<AlarmManagerService.ChargingReceiver> chargingReceiverCaptor =
+ ArgumentCaptor.forClass(AlarmManagerService.ChargingReceiver.class);
+ verify(mMockContext).registerReceiver(chargingReceiverCaptor.capture(),
+ argThat((filter) -> filter.hasAction(BatteryManager.ACTION_CHARGING)
+ && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
+ mChargingReceiver = chargingReceiverCaptor.getValue();
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
@@ -724,17 +734,19 @@
}
private void assertAndHandleParoleChanged(boolean parole) {
- mAppStandbyListener.onParoleStateChanged(parole);
+ mChargingReceiver.onReceive(mMockContext,
+ new Intent(parole ? BatteryManager.ACTION_CHARGING
+ : BatteryManager.ACTION_DISCHARGING));
final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
final Message lastMessage = messageCaptor.getValue();
assertEquals("Unexpected message send to handler", lastMessage.what,
- APP_STANDBY_PAROLE_CHANGED);
+ CHARGING_STATUS_CHANGED);
mService.mHandler.handleMessage(lastMessage);
}
@Test
- public void testParole() throws Exception {
+ public void testCharging() throws Exception {
setQuotasEnabled(true);
final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index d0158e0..247a358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -64,6 +64,9 @@
import android.util.ArraySet;
import android.util.Pair;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.server.AppStateTracker.Listener;
@@ -85,14 +88,10 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
/**
* Tests for {@link AppStateTracker}
*
- * Run with:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+ * Run with: atest com.android.server.AppStateTrackerTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 9180054..d70e164 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -207,14 +207,14 @@
}
@Test
- public void serviceDisconnected_removeServiceAndAddToBinding() {
+ public void serviceDisconnected_removeServiceAndAddToCrashed() {
when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
mUserState.addServiceLocked(mMockConnection);
mUserState.serviceDisconnectedLocked(mMockConnection);
assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
- assertTrue(mUserState.getBindingServicesLocked().contains(COMPONENT_NAME));
+ assertTrue(mUserState.getCrashedServicesLocked().contains(COMPONENT_NAME));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c3ef832..aeccfc5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,10 +25,12 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
+import static android.app.admin.PasswordMetrics.computeForPassword;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.server.testutils.TestUtils.assertExpectException;
@@ -4295,11 +4297,7 @@
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
- 8, 2,
- 6, 1,
- 0, 1);
+ PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
setActivePasswordState(passwordMetricsNoSymbols);
assertTrue(dpm.isActivePasswordSufficient());
@@ -4326,11 +4324,7 @@
reset(mContext.spiedContext);
assertFalse(dpm.isActivePasswordSufficient());
- PasswordMetrics passwordMetricsWithSymbols = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
- 7, 2,
- 5, 1,
- 1, 2);
+ PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes());
setActivePasswordState(passwordMetricsWithSymbols);
assertTrue(dpm.isActivePasswordSufficient());
@@ -4347,7 +4341,7 @@
final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
// When there is no lockscreen, user password metrics is always empty.
when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
- .thenReturn(new PasswordMetrics());
+ .thenReturn(new PasswordMetrics(CREDENTIAL_TYPE_NONE));
// If no password requirements are set, isActivePasswordSufficient should succeed.
assertTrue(dpm.isActivePasswordSufficient());
@@ -5314,7 +5308,7 @@
.thenReturn(DpmMockContext.CALLER_USER_HANDLE);
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
- .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPassword("asdf".getBytes()));
assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
}
@@ -5331,10 +5325,10 @@
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
- .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPassword("asdf".getBytes()));
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(parentUser.id))
- .thenReturn(PasswordMetrics.computeForPassword("parentUser".getBytes()));
+ .thenReturn(computeForPassword("parentUser".getBytes()));
assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
}
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
index fe7a376..25b41db 100644
--- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -29,7 +29,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
-import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.text.TextUtils;
import android.util.Log;
@@ -88,17 +87,11 @@
private static final int REPEAT_TEST_COUNT = 5;
- private static final String KEY_PAROLE_DURATION = "parole_duration";
- private static final String DESIRED_PAROLE_DURATION = "0";
-
private static Context mContext;
private static UiDevice mUiDevice;
private static int mTestPkgUid;
private static BatteryManager mBatteryManager;
- private static boolean mAppIdleConstsUpdated;
- private static String mOriginalAppIdleConsts;
-
private static ServiceConnection mServiceConnection;
private static ICmdReceiverService mCmdReceiverService;
@@ -107,7 +100,6 @@
mContext = InstrumentationRegistry.getContext();
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- setDesiredParoleDuration();
mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0);
@@ -119,10 +111,6 @@
@AfterClass
public static void tearDownOnce() throws Exception {
batteryReset();
- if (mAppIdleConstsUpdated) {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS, mOriginalAppIdleConsts);
- }
unbindService();
}
@@ -160,27 +148,6 @@
}
}
- private static void setDesiredParoleDuration() {
- mOriginalAppIdleConsts = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS);
- String newAppIdleConstants;
- final String newConstant = KEY_PAROLE_DURATION + "=" + DESIRED_PAROLE_DURATION;
- if (mOriginalAppIdleConsts == null || "null".equals(mOriginalAppIdleConsts)) {
- // app_idle_constants is initially empty, so just assign the desired value.
- newAppIdleConstants = newConstant;
- } else if (mOriginalAppIdleConsts.contains(KEY_PAROLE_DURATION)) {
- // app_idle_constants contains parole_duration, so replace it with the desired value.
- newAppIdleConstants = mOriginalAppIdleConsts.replaceAll(
- KEY_PAROLE_DURATION + "=\\d+", newConstant);
- } else {
- // app_idle_constants didn't have parole_duration, so append the desired value.
- newAppIdleConstants = mOriginalAppIdleConsts + "," + newConstant;
- }
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS, newAppIdleConstants);
- mAppIdleConstsUpdated = true;
- }
-
@Test
public void testStartActivity_batterySaver() throws Exception {
setBatterySaverMode(true);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 4ffcf8f..12ba219 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -47,7 +47,6 @@
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.ContextWrapper;
@@ -77,7 +76,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
@@ -108,8 +106,6 @@
private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS;
private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS;
private static final long RARE_THRESHOLD = 48 * HOUR_MS;
- // Short STABLE_CHARGING_THRESHOLD for testing purposes
- private static final long STABLE_CHARGING_THRESHOLD = 2000;
/** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */
private static boolean isPackageInstalled = true;
@@ -132,7 +128,6 @@
static class MyInjector extends AppStandbyController.Injector {
long mElapsedRealtime;
boolean mIsAppIdleEnabled = true;
- boolean mIsCharging;
List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
boolean mDisplayOn;
DisplayManager.DisplayListener mDisplayListener;
@@ -167,11 +162,6 @@
}
@Override
- boolean isCharging() {
- return mIsCharging;
- }
-
- @Override
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mPowerSaveWhitelistExceptIdle.contains(packageName);
}
@@ -228,8 +218,7 @@
return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/"
+ WORKING_SET_THRESHOLD + "/"
+ FREQUENT_THRESHOLD + "/"
- + RARE_THRESHOLD + ","
- + "stable_charging_threshold=" + STABLE_CHARGING_THRESHOLD;
+ + RARE_THRESHOLD;
}
@Override
@@ -273,13 +262,6 @@
} catch (PackageManager.NameNotFoundException nnfe) {}
}
- private void setChargingState(AppStandbyController controller, boolean charging) {
- mInjector.mIsCharging = charging;
- if (controller != null) {
- controller.setChargingState(charging);
- }
- }
-
private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
mInjector.mIsAppIdleEnabled = enabled;
if (controller != null) {
@@ -296,7 +278,6 @@
controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mInjector.setDisplayOn(false);
mInjector.setDisplayOn(true);
- setChargingState(controller, false);
controller.checkIdleStates(USER_ID);
assertNotEquals(STANDBY_BUCKET_EXEMPTED,
controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -314,65 +295,6 @@
MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
mInjector = new MyInjector(myContext, Looper.getMainLooper());
mController = setupController();
- setChargingState(mController, false);
- }
-
- private class TestParoleListener extends UsageStatsManagerInternal.AppIdleStateChangeListener {
- private boolean mOnParole = false;
- private CountDownLatch mLatch;
- private long mLastParoleChangeTime;
- private boolean mIsExpecting = false;
- private boolean mExpectedParoleState;
-
- public boolean getParoleState() {
- synchronized (this) {
- return mOnParole;
- }
- }
-
- public void rearmLatch() {
- synchronized (this) {
- mLatch = new CountDownLatch(1);
- mIsExpecting = false;
- }
- }
-
- public void rearmLatch(boolean expectedParoleState) {
- synchronized (this) {
- mLatch = new CountDownLatch(1);
- mIsExpecting = true;
- mExpectedParoleState = expectedParoleState;
- }
- }
-
- public void awaitOnLatch(long time) throws Exception {
- mLatch.await(time, TimeUnit.MILLISECONDS);
- }
-
- public long getLastParoleChangeTime() {
- synchronized (this) {
- return mLastParoleChangeTime;
- }
- }
-
- @Override
- public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
- int bucket, int reason) {
- }
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- synchronized (this) {
- // Only record information if it is being looked for
- if (mLatch != null && mLatch.getCount() > 0) {
- mOnParole = isParoleOn;
- mLastParoleChangeTime = getCurrentTime();
- if (!mIsExpecting || isParoleOn == mExpectedParoleState) {
- mLatch.countDown();
- }
- }
- }
- }
}
@Test
@@ -383,133 +305,6 @@
mInjector.mElapsedRealtime, false));
}
- @Test
- public void testCharging() throws Exception {
- long startTime;
- TestParoleListener paroleListener = new TestParoleListener();
- long marginOfError = 200;
-
- // Charging
- paroleListener.rearmLatch();
- mController.addListener(paroleListener);
- startTime = getCurrentTime();
- setChargingState(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- // Parole will only be granted after device has been charging for a sufficient amount of
- // time.
- assertEquals(STABLE_CHARGING_THRESHOLD,
- paroleListener.getLastParoleChangeTime() - startTime,
- marginOfError);
-
- // Discharging
- paroleListener.rearmLatch();
- startTime = getCurrentTime();
- setChargingState(mController, false);
- mController.checkIdleStates(USER_ID);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.getParoleState());
- // Parole should be revoked immediately
- assertEquals(0,
- paroleListener.getLastParoleChangeTime() - startTime,
- marginOfError);
-
- // Brief Charging
- paroleListener.rearmLatch();
- setChargingState(mController, true);
- setChargingState(mController, false);
- // Device stopped charging before the stable charging threshold.
- // Parole should not be granted at the end of the threshold
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.getParoleState());
-
- // Charging Again
- paroleListener.rearmLatch();
- startTime = getCurrentTime();
- setChargingState(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.getParoleState());
- assertTrue(paroleListener.mOnParole);
- assertEquals(STABLE_CHARGING_THRESHOLD,
- paroleListener.getLastParoleChangeTime() - startTime,
- marginOfError);
- }
-
- @Test
- public void testEnabledState() throws Exception {
- TestParoleListener paroleListener = new TestParoleListener();
- paroleListener.rearmLatch(true);
- mController.addListener(paroleListener);
- long lastUpdateTime;
-
- // Test that listeners are notified if enabled changes when the device is not in parole.
- setChargingState(mController, false);
-
- // Start off not enabled. Device is effectively in permanent parole.
- setAppIdleEnabled(mController, false);
- // Since AppStandbyController uses a handler to notify listeners of a state change, there is
- // some inherent latency between changing the state and getting the notification. We need to
- // wait until the paroleListener has been notified that parole is on before continuing with
- // the test.
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
-
- // Enable controller
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.mOnParole);
- lastUpdateTime = paroleListener.getLastParoleChangeTime();
-
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.mOnParole);
- // Make sure AppStandbyController doesn't notify listeners when there's no change.
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
-
- // Disable controller
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- lastUpdateTime = paroleListener.getLastParoleChangeTime();
-
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- // Make sure AppStandbyController doesn't notify listeners when there's no change.
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
-
-
- // Test that listeners aren't notified if enabled status changes when the device is already
- // in parole.
-
- // A device is in parole whenever it's charging.
- setChargingState(mController, true);
-
- // Start off not enabled.
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- lastUpdateTime = paroleListener.getLastParoleChangeTime();
-
- // Test that toggling doesn't notify the listener.
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
-
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
- }
-
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
mInjector.mElapsedRealtime = elapsedTime;
controller.checkIdleStates(USER_ID);
@@ -804,8 +599,6 @@
@Test
public void testSystemInteractionTimeout() throws Exception {
- setChargingState(mController, false);
-
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
// Fast forward to RARE
mInjector.mElapsedRealtime = RARE_THRESHOLD + 100;
@@ -829,8 +622,6 @@
@Test
public void testInitialForegroundServiceTimeout() throws Exception {
- setChargingState(mController, false);
-
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ecee709..2cd207f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -97,8 +97,6 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
@@ -191,11 +189,6 @@
event.mPackage = packageName;
reportEventOrAddToQueue(userId, event);
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
-
- }
};
public UsageStatsService(Context context) {
@@ -1426,7 +1419,7 @@
Binder.getCallingUid(), userId);
final long token = Binder.clearCallingIdentity();
try {
- return mAppStandby.isAppIdleFilteredOrParoled(
+ return mAppStandby.isAppIdleFiltered(
packageName, userId,
SystemClock.elapsedRealtime(), obfuscateInstantApps);
} finally {
@@ -1995,11 +1988,6 @@
}
@Override
- public boolean isAppIdleParoleOn() {
- return mAppStandby.isParoledOrCharging();
- }
-
- @Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
@@ -2015,7 +2003,6 @@
@Override
public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
mAppStandby.addListener(listener);
- listener.onParoleStateChanged(isAppIdleParoleOn());
}
@Override
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
index cf120cf..59f4d56 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -33,6 +33,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -162,8 +163,8 @@
@Override
public boolean equals(Object other) {
if (other instanceof BaseWithActivityRecordData) {
- return activityRecordSnapshot.equals(
- ((BaseWithActivityRecordData)other).activityRecordSnapshot) &&
+ return (Arrays.equals(activityRecordSnapshot,
+ ((BaseWithActivityRecordData)other).activityRecordSnapshot)) &&
super.equals(other);
}
return false;
@@ -171,7 +172,7 @@
@Override
protected String toStringBody() {
- return ", " + activityRecordSnapshot.toString();
+ return ", " + new String(activityRecordSnapshot);
}
@Override
@@ -208,7 +209,7 @@
@Override
protected String toStringBody() {
- return ", temperature=" + Integer.toString(temperature);
+ return super.toStringBody() + ", temperature=" + Integer.toString(temperature);
}
@Override
@@ -235,7 +236,7 @@
@Override
public boolean equals(Object other) {
- if (other instanceof ActivityLaunched) {
+ if (other instanceof ActivityLaunchFinished) {
return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
super.equals(other);
}
@@ -244,7 +245,7 @@
@Override
protected String toStringBody() {
- return ", timestampNs=" + Long.toString(timestampNs);
+ return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
}
@Override
@@ -271,8 +272,8 @@
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunchCancelled) {
- return Objects.equals(activityRecordSnapshot,
- ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
+ return Arrays.equals(activityRecordSnapshot,
+ ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
super.equals(other);
}
return false;
@@ -280,7 +281,7 @@
@Override
protected String toStringBody() {
- return ", " + activityRecordSnapshot.toString();
+ return super.toStringBody() + ", " + new String(activityRecordSnapshot);
}
@Override
@@ -325,7 +326,7 @@
@Override
protected String toStringBody() {
- return ", timestampNs=" + Long.toString(timestampNs);
+ return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
}
@Override
diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt
new file mode 100644
index 0000000..51e407d
--- /dev/null
+++ b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.google.android.startop.iorap
+
+import android.content.Intent;
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import com.google.android.startop.iorap.AppLaunchEvent;
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunched
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchCancelled
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchFinished
+import com.google.android.startop.iorap.AppLaunchEvent.IntentStarted;
+import com.google.android.startop.iorap.AppLaunchEvent.IntentFailed;
+import com.google.android.startop.iorap.AppLaunchEvent.ReportFullyDrawn
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+
+/**
+ * Basic unit tests to test all of the [AppLaunchEvent]s in [com.google.android.startop.iorap].
+ */
+@SmallTest
+class AppLaunchEventTest {
+ /**
+ * Test for IntentStarted.
+ */
+ @Test
+ fun testIntentStarted() {
+ var intent = Intent()
+ val valid = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
+ val copy = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
+ val noneCopy1 = IntentStarted(/* sequenceId= */1L, intent, /* timestampNs= */ 1L)
+ val noneCopy2 = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 2L)
+ val noneCopy3 = IntentStarted(/* sequenceId= */2L, Intent(), /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("IntentStarted{sequenceId=2, intent=Intent { } , timestampNs=1}")
+ }
+
+ /**
+ * Test for IntentFailed.
+ */
+ @Test
+ fun testIntentFailed() {
+ val valid = IntentFailed(/* sequenceId= */2L)
+ val copy = IntentFailed(/* sequenceId= */2L)
+ val noneCopy = IntentFailed(/* sequenceId= */1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("IntentFailed{sequenceId=2}")
+ }
+
+ /**
+ * Test for ActivityLaunched.
+ */
+ @Test
+ fun testActivityLaunched() {
+ //var activityRecord =
+ val valid = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val copy = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val noneCopy1 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val noneCopy2 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
+ /* temperature= */ 1)
+ val noneCopy3 = ActivityLaunched(/* sequenceId= */1L, "test1".toByteArray(),
+ /* temperature= */ 0)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunched{sequenceId=2, test, temperature=0}")
+ }
+
+
+ /**
+ * Test for ActivityLaunchFinished.
+ */
+ @Test
+ fun testActivityLaunchFinished() {
+ val valid = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val copy = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy1 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy2 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 2L)
+ val noneCopy3 = ActivityLaunchFinished(/* sequenceId= */2L, "test1".toByteArray(),
+ /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunchFinished{sequenceId=2, test, timestampNs=1}")
+ }
+
+ /**
+ * Test for ActivityLaunchCancelled.
+ */
+ @Test
+ fun testActivityLaunchCancelled() {
+ val valid = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
+ val copy = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
+ val noneCopy1 = ActivityLaunchCancelled(/* sequenceId= */1L, "test".toByteArray())
+ val noneCopy2 = ActivityLaunchCancelled(/* sequenceId= */2L, "test1".toByteArray())
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunchCancelled{sequenceId=2, test}")
+ }
+
+ /**
+ * Test for ReportFullyDrawn.
+ */
+ @Test
+ fun testReportFullyDrawn() {
+ val valid = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
+ val copy = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
+ val noneCopy1 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy2 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy3 = ReportFullyDrawn(/* sequenceId= */2L, "test1".toByteArray(),
+ /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ReportFullyDrawn{sequenceId=2, test, timestampNs=1}")
+ }
+}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 5e71416..3f348a4 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -728,6 +728,7 @@
}
/** {@hide} */
+ @TestApi
public String getTelecomCallId() {
return mTelecomCallId;
}
@@ -2137,6 +2138,9 @@
}
int state = parcelableCall.getState();
+ if (mTargetSdkVersion < Phone.SDK_VERSION_R && state == Call.STATE_SIMULATED_RINGING) {
+ state = Call.STATE_RINGING;
+ }
boolean stateChanged = mState != state;
if (stateChanged) {
mState = state;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 0d97567..ef1c790 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -374,6 +374,8 @@
new ComponentName(getPackageName(), getClass().getName()));
} else if (response.getSilenceCall()) {
mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
+ } else if (response.getShouldScreenCallFurther()) {
+ mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId());
} else {
mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
}
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 0cc052e..2ecdb30 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -21,7 +21,6 @@
import android.bluetooth.BluetoothDevice;
import android.os.Build;
import android.os.Bundle;
-import android.os.RemoteException;
import android.util.ArrayMap;
import java.util.Collections;
@@ -111,6 +110,10 @@
public void onSilenceRinger(Phone phone) { }
}
+ // TODO: replace all usages of this with the actual R constant from Build.VERSION_CODES
+ /** @hide */
+ public static final int SDK_VERSION_R = 30;
+
// A Map allows us to track each Call by its Telecom-specified call ID
private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
@@ -143,6 +146,12 @@
}
final void internalAddCall(ParcelableCall parcelableCall) {
+ if (mTargetSdkVersion < SDK_VERSION_R
+ && parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
+ Log.i(this, "Skipping adding audio processing call for sdk compatibility");
+ return;
+ }
+
Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
mCallByTelecomCallId.put(parcelableCall.getId(), call);
@@ -150,7 +159,7 @@
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
fireCallAdded(call);
- }
+ }
final void internalRemoveCall(Call call) {
mCallByTelecomCallId.remove(call.internalGetCallId());
@@ -164,12 +173,28 @@
}
final void internalUpdateCall(ParcelableCall parcelableCall) {
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
- if (call != null) {
- checkCallTree(parcelableCall);
- call.internalUpdate(parcelableCall, mCallByTelecomCallId);
- }
- }
+ if (mTargetSdkVersion < SDK_VERSION_R
+ && parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
+ Log.i(this, "removing audio processing call during update for sdk compatibility");
+ Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ if (call != null) {
+ internalRemoveCall(call);
+ }
+ return;
+ }
+
+ Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ if (call != null) {
+ checkCallTree(parcelableCall);
+ call.internalUpdate(parcelableCall, mCallByTelecomCallId);
+ } else {
+ // This call may have come out of audio processing. Try adding it if our target sdk
+ // version is low enough.
+ if (mTargetSdkVersion < SDK_VERSION_R) {
+ internalAddCall(parcelableCall);
+ }
+ }
+ }
final void internalSetPostDialWait(String telecomId, String remaining) {
Call call = mCallByTelecomCallId.get(telecomId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 9e60afc..047b220 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -526,6 +526,15 @@
"default_vm_number_roaming_string";
/**
+ * Where there is no preloaded voicemail number on a SIM card, specifies the carrier's default
+ * voicemail number while the device is both roaming and not registered for IMS.
+ * When empty string, no default voicemail number is specified for roaming network and
+ * unregistered state in IMS.
+ */
+ public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING =
+ "default_vm_number_roaming_and_ims_unregistered_string";
+
+ /**
* Flag that specifies to use the user's own phone number as the voicemail number when there is
* no pre-loaded voicemail number on the SIM card.
* <p>
@@ -3232,6 +3241,7 @@
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_ROAMING_STRING, "");
+ sDefaults.putString(KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING, "");
sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 40d057f..8425ec1 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -47,6 +47,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
@@ -58,10 +59,8 @@
import android.util.DisplayMetrics;
import android.util.Log;
-import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ISetOpportunisticDataCallback;
import com.android.internal.telephony.ISub;
-import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Preconditions;
@@ -923,20 +922,24 @@
OnSubscriptionsChangedListenerHandler(Looper looper) {
super(looper);
}
-
- @Override
- public void handleMessage(Message msg) {
- if (DBG) {
- log("handleMessage: invoke the overriden onSubscriptionsChanged()");
- }
- OnSubscriptionsChangedListener.this.onSubscriptionsChanged();
- }
}
- private final Handler mHandler;
+ /**
+ * Posted executor callback on the handler associated with a given looper.
+ * The looper can be the calling thread's looper or the looper passed from the
+ * constructor {@link #OnSubscriptionsChangedListener(Looper)}.
+ */
+ private final HandlerExecutor mExecutor;
+
+ /**
+ * @hide
+ */
+ public HandlerExecutor getHandlerExecutor() {
+ return mExecutor;
+ }
public OnSubscriptionsChangedListener() {
- mHandler = new OnSubscriptionsChangedListenerHandler();
+ mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler());
}
/**
@@ -945,7 +948,7 @@
* @hide
*/
public OnSubscriptionsChangedListener(Looper looper) {
- mHandler = new OnSubscriptionsChangedListenerHandler(looper);
+ mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler(looper));
}
/**
@@ -957,18 +960,6 @@
if (DBG) log("onSubscriptionsChanged: NOT OVERRIDDEN");
}
- /**
- * The callback methods need to be called on the handler thread where
- * this object was created. If the binder did that for us it'd be nice.
- */
- IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
- @Override
- public void onSubscriptionsChanged() {
- if (DBG) log("callback: received, sendEmptyMessage(0) to handler");
- mHandler.sendEmptyMessage(0);
- }
- };
-
private void log(String s) {
Rlog.d(LOG_TAG, s);
}
@@ -1010,21 +1001,19 @@
* onSubscriptionsChanged overridden.
*/
public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
+ if (listener == null) return;
String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
if (DBG) {
logd("register OnSubscriptionsChangedListener pkgName=" + pkgName
+ " listener=" + listener);
}
- try {
- // We use the TelephonyRegistry as it runs in the system and thus is always
- // available. Where as SubscriptionController could crash and not be available
- ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- if (tr != null) {
- tr.addOnSubscriptionsChangedListener(pkgName, listener.callback);
- }
- } catch (RemoteException ex) {
- Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
+ // We use the TelephonyRegistry as it runs in the system and thus is always
+ // available. Where as SubscriptionController could crash and not be available
+ TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
+ mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+ if (telephonyRegistryManager != null) {
+ telephonyRegistryManager.addOnSubscriptionsChangedListener(listener,
+ listener.mExecutor);
}
}
@@ -1036,21 +1025,18 @@
* @param listener that is to be unregistered.
*/
public void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
+ if (listener == null) return;
String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
if (DBG) {
logd("unregister OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug
+ " listener=" + listener);
}
- try {
- // We use the TelephonyRegistry as it runs in the system and thus is always
- // available where as SubscriptionController could crash and not be available
- ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- if (tr != null) {
- tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
- }
- } catch (RemoteException ex) {
- Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
+ // We use the TelephonyRegistry as it runs in the system and thus is always
+ // available where as SubscriptionController could crash and not be available
+ TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
+ mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+ if (telephonyRegistryManager != null) {
+ telephonyRegistryManager.removeOnSubscriptionsChangedListener(listener);
}
}
@@ -1069,7 +1055,6 @@
* for #onOpportunisticSubscriptionsChanged to be invoked.
*/
public static class OnOpportunisticSubscriptionsChangedListener {
- private Executor mExecutor;
/**
* Callback invoked when there is any change to any SubscriptionInfo. Typically
* this method would invoke {@link #getActiveSubscriptionInfoList}
@@ -1078,27 +1063,6 @@
if (DBG) log("onOpportunisticSubscriptionsChanged: NOT OVERRIDDEN");
}
- private void setExecutor(Executor executor) {
- mExecutor = executor;
- }
-
- /**
- * The callback methods need to be called on the handler thread where
- * this object was created. If the binder did that for us it'd be nice.
- */
- IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
- @Override
- public void onSubscriptionsChanged() {
- final long identity = Binder.clearCallingIdentity();
- try {
- if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
- mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- };
-
private void log(String s) {
Rlog.d(LOG_TAG, s);
}
@@ -1125,18 +1089,13 @@
+ " listener=" + listener);
}
- listener.setExecutor(executor);
-
- try {
- // We use the TelephonyRegistry as it runs in the system and thus is always
- // available. Where as SubscriptionController could crash and not be available
- ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- if (tr != null) {
- tr.addOnOpportunisticSubscriptionsChangedListener(pkgName, listener.callback);
- }
- } catch (RemoteException ex) {
- Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
+ // We use the TelephonyRegistry as it runs in the system and thus is always
+ // available where as SubscriptionController could crash and not be available
+ TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
+ mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+ if (telephonyRegistryManager != null) {
+ telephonyRegistryManager.addOnOpportunisticSubscriptionsChangedListener(
+ listener, executor);
}
}
@@ -1156,16 +1115,10 @@
logd("unregister OnOpportunisticSubscriptionsChangedListener pkgForDebug="
+ pkgForDebug + " listener=" + listener);
}
- try {
- // We use the TelephonyRegistry as it runs in the system and thus is always
- // available where as SubscriptionController could crash and not be available
- ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- if (tr != null) {
- tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
- }
- } catch (RemoteException ex) {
- Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex);
+ TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
+ mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+ if (telephonyRegistryManager != null) {
+ telephonyRegistryManager.removeOnOpportunisticSubscriptionsChangedListener(listener);
}
}
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index 3515053..325c1c0 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,7 +32,7 @@
- // Code below generated by codegen v1.0.8.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -58,7 +58,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(android.os.Parcel dest, int flags) {
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -72,7 +72,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected HierrarchicalDataClassBase(android.os.Parcel in) {
+ protected HierrarchicalDataClassBase(@android.annotation.NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -92,14 +92,14 @@
}
@Override
- public HierrarchicalDataClassBase createFromParcel(android.os.Parcel in) {
+ public HierrarchicalDataClassBase createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
return new HierrarchicalDataClassBase(in);
}
};
@DataClass.Generated(
- time = 1570828332402L,
- codegenVersion = "1.0.8",
+ time = 1571258914826L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index c867409..6c92009 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,7 +46,7 @@
- // Code below generated by codegen v1.0.8.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -74,7 +74,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -90,7 +90,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected HierrarchicalDataClassChild(android.os.Parcel in) {
+ protected HierrarchicalDataClassChild(@NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -114,14 +114,14 @@
}
@Override
- public HierrarchicalDataClassChild createFromParcel(android.os.Parcel in) {
+ public HierrarchicalDataClassChild createFromParcel(@NonNull android.os.Parcel in) {
return new HierrarchicalDataClassChild(in);
}
};
@DataClass.Generated(
- time = 1570828333399L,
- codegenVersion = "1.0.8",
+ time = 1571258915848L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index 8d097a0..36def8a 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -52,7 +52,7 @@
- // Code below generated by codegen v1.0.8.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -161,7 +161,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -185,7 +185,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected ParcelAllTheThingsDataClass(Parcel in) {
+ protected ParcelAllTheThingsDataClass(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -237,7 +237,7 @@
}
@Override
- public ParcelAllTheThingsDataClass createFromParcel(Parcel in) {
+ public ParcelAllTheThingsDataClass createFromParcel(@NonNull Parcel in) {
return new ParcelAllTheThingsDataClass(in);
}
};
@@ -410,8 +410,8 @@
}
@DataClass.Generated(
- time = 1570828331396L,
- codegenVersion = "1.0.8",
+ time = 1571258913802L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index d014d6d..c444d61 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,7 +342,7 @@
- // Code below generated by codegen v1.0.8.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -1119,7 +1119,7 @@
@Override
@DataClass.Generated.Member
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(SampleDataClass other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -1184,8 +1184,8 @@
@DataClass.Generated.Member
void forEachField(
- DataClass.PerIntFieldAction<SampleDataClass> actionInt,
- DataClass.PerObjectFieldAction<SampleDataClass> actionObject) {
+ @NonNull DataClass.PerIntFieldAction<SampleDataClass> actionInt,
+ @NonNull DataClass.PerObjectFieldAction<SampleDataClass> actionObject) {
actionInt.acceptInt(this, "num", mNum);
actionInt.acceptInt(this, "num2", mNum2);
actionInt.acceptInt(this, "num4", mNum4);
@@ -1211,7 +1211,7 @@
/** @deprecated May cause boxing allocations - use with caution! */
@Deprecated
@DataClass.Generated.Member
- void forEachField(DataClass.PerObjectFieldAction<SampleDataClass> action) {
+ void forEachField(@NonNull DataClass.PerObjectFieldAction<SampleDataClass> action) {
action.acceptObject(this, "num", mNum);
action.acceptObject(this, "num2", mNum2);
action.acceptObject(this, "num4", mNum4);
@@ -1258,7 +1258,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -1297,7 +1297,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ SampleDataClass(Parcel in) {
+ /* package-private */ SampleDataClass(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -1420,7 +1420,7 @@
}
@Override
- public SampleDataClass createFromParcel(Parcel in) {
+ public SampleDataClass createFromParcel(@NonNull Parcel in) {
return new SampleDataClass(in);
}
};
@@ -1872,8 +1872,8 @@
}
@DataClass.Generated(
- time = 1570828329319L,
- codegenVersion = "1.0.8",
+ time = 1571258911688L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index 1c87e8f..55feae7 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,7 +85,7 @@
- // Code below generated by codegen v1.0.8.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -142,7 +142,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -158,7 +158,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected SampleWithCustomBuilder(Parcel in) {
+ protected SampleWithCustomBuilder(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -184,7 +184,7 @@
}
@Override
- public SampleWithCustomBuilder createFromParcel(Parcel in) {
+ public SampleWithCustomBuilder createFromParcel(@NonNull Parcel in) {
return new SampleWithCustomBuilder(in);
}
};
@@ -253,8 +253,8 @@
}
@DataClass.Generated(
- time = 1570828330331L,
- codegenVersion = "1.0.8",
+ time = 1571258912752L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index 27af37f..b967f19 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -51,7 +51,7 @@
- // Code below generated by codegen v1.0.8.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -65,8 +65,8 @@
@DataClass.Generated(
- time = 1570828334384L,
- codegenVersion = "1.0.8",
+ time = 1571258916868L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
inputSignatures = "public @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 0ebb3cf..431f378 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -417,7 +417,7 @@
if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) {
+"@Override"
+GENERATED_MEMBER_HEADER
- "public void writeToParcel($Parcel dest, int flags)" {
+ "public void writeToParcel(@$NonNull $Parcel dest, int flags)" {
+"// You can override field parcelling by defining methods like:"
+"// void parcelFieldName(Parcel dest, int flags) { ... }"
+""
@@ -473,7 +473,7 @@
+"/** @hide */"
+"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
+GENERATED_MEMBER_HEADER
- "$visibility $ClassName($Parcel in) {" {
+ "$visibility $ClassName(@$NonNull $Parcel in) {" {
+"// You can override field unparcelling by defining methods like:"
+"// static FieldType unparcelFieldName(Parcel in) { ... }"
+""
@@ -598,7 +598,7 @@
}
+"@Override"
- "public $ClassName createFromParcel($Parcel in)" {
+ "public $ClassName createFromParcel(@$NonNull $Parcel in)" {
+"return new $ClassName(in);"
}
rmEmptyLine()
@@ -611,7 +611,7 @@
if (!isMethodGenerationSuppressed("equals", "Object")) {
+"@Override"
+GENERATED_MEMBER_HEADER
- "public boolean equals(Object o)" {
+ "public boolean equals(@$Nullable Object o)" {
+"// You can override field equality logic by defining either of the methods like:"
+"// boolean fieldNameEquals($ClassName other) { ... }"
+"// boolean fieldNameEquals(FieldType otherValue) { ... }"
@@ -904,7 +904,7 @@
usedSpecializationsSet.toList().forEachLastAware { specType, isLast ->
val SpecType = specType.capitalize()
val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction")
- +"$ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
+ +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
}
}; " {" {
usedSpecializations.forEachIndexed { i, specType ->
@@ -919,7 +919,7 @@
+"/** @deprecated May cause boxing allocations - use with caution! */"
+"@Deprecated"
+GENERATED_MEMBER_HEADER
- "void forEachField($PerObjectFieldAction<$ClassType> action)" {
+ "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" {
fields.forEachApply {
+"action.acceptObject(this, \"$nameLowerCamel\", $name);"
}
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 8c4583f..3eb9e7b 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.8"
+const val CODEGEN_VERSION = "1.0.9"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"