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"